feat(frontend): Refactor organization, fixed several organization bugs
This commit is contained in:
@@ -34,13 +34,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import * as z from 'zod'
|
||||
import { useBetterAuth } from '~/composables/useBetterAuth'
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'organizationCreated', id: string | undefined): void
|
||||
}>()
|
||||
|
||||
const { createOrganization } = useBetterAuth()
|
||||
const { createOrganization } = useOrganizationStore()
|
||||
const open = ref(false)
|
||||
const loading = ref(false)
|
||||
const isSlugEdited = ref(false)
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { CommentDto, FormElementDto, FormOptionDto } from '~/.api-client'
|
||||
import { useComment } from '~/composables/comment/useComment'
|
||||
import { useCommentTextarea } from '~/composables/comment/useCommentTextarea'
|
||||
import { resolveComponent } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
@@ -69,7 +69,7 @@ const commentStore = useCommentStore()
|
||||
const { load: loadComments } = commentStore
|
||||
const { comments } = storeToRefs(commentStore)
|
||||
|
||||
const commentActions = useComment(props.applicationFormId)
|
||||
const commentActions = useCommentTextarea(props.applicationFormId)
|
||||
const {
|
||||
submitComment,
|
||||
updateEditComment,
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
import { ROLES, type LegalRole } from '~/server/utils/permissions'
|
||||
|
||||
const { canInviteMembers } = usePermissions()
|
||||
const { inviteMember } = useBetterAuth()
|
||||
const { inviteMember } = useOrganizationStore()
|
||||
|
||||
const open = ref(false)
|
||||
const loading = ref(false)
|
||||
@@ -114,7 +114,6 @@ async function handleSubmit() {
|
||||
try {
|
||||
await inviteMember(form.value.email, form.value.role)
|
||||
open.value = false
|
||||
useToast().add({ title: 'Einladung gesendet', color: 'success' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import type { CreateCommentDto, CommentDto } from '~/.api-client'
|
||||
|
||||
export function useComment(applicationFormId: string) {
|
||||
export function useCommentTextarea(applicationFormId: string) {
|
||||
const commentStore = useCommentStore()
|
||||
const { createComment, updateComment } = commentStore
|
||||
const { user } = useAuth()
|
||||
const isEditingComment = ref(false)
|
||||
const currentEditedComment = ref<CommentDto | null>(null)
|
||||
const commentTextAreaValue = ref('')
|
||||
const toast = useToast()
|
||||
|
||||
async function submitComment(formElementId: string) {
|
||||
const newCommentDto: CreateCommentDto = {
|
||||
@@ -15,9 +16,9 @@ export function useComment(applicationFormId: string) {
|
||||
try {
|
||||
await createComment(applicationFormId, formElementId, newCommentDto)
|
||||
commentTextAreaValue.value = ''
|
||||
useToast().add({ title: 'Comment created successfully', color: 'success' })
|
||||
toast.add({ title: 'Comment created successfully', color: 'success' })
|
||||
} catch (e) {
|
||||
useToast().add({ title: 'Error creating comment', color: 'error' })
|
||||
toast.add({ title: 'Error creating comment', color: 'error' })
|
||||
console.error('Error creating comment:', e)
|
||||
}
|
||||
}
|
||||
@@ -30,9 +31,9 @@ export function useComment(applicationFormId: string) {
|
||||
commentTextAreaValue.value = ''
|
||||
currentEditedComment.value = null
|
||||
isEditingComment.value = false
|
||||
useToast().add({ title: 'Comment updated successfully', color: 'success' })
|
||||
toast.add({ title: 'Comment updated successfully', color: 'success' })
|
||||
} catch (e) {
|
||||
useToast().add({ title: 'Error updating comment', color: 'error' })
|
||||
toast.add({ title: 'Error updating comment', color: 'error' })
|
||||
console.error('Error updating comment:', e)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { LegalRole } from '~/server/utils/permissions'
|
||||
|
||||
export function useOrganizationApi() {
|
||||
const { organization } = useAuth()
|
||||
|
||||
async function createOrganization(name: string, slug: string, logo?: string) {
|
||||
return organization.create({ name, slug, logo })
|
||||
}
|
||||
|
||||
async function deleteOrganization(organizationId: string) {
|
||||
return organization.delete({ organizationId })
|
||||
}
|
||||
|
||||
async function getInvitation(invitationId: string) {
|
||||
return organization.getInvitation({ query: { id: invitationId } })
|
||||
}
|
||||
|
||||
async function listInvitations(organizationId?: string) {
|
||||
return organization.listInvitations(organizationId ? { query: { organizationId: organizationId } } : undefined)
|
||||
}
|
||||
|
||||
async function inviteMember(email: string, role: LegalRole) {
|
||||
return organization.inviteMember({ email, role })
|
||||
}
|
||||
|
||||
async function acceptInvitation(invitationId: string) {
|
||||
return organization.acceptInvitation({ invitationId })
|
||||
}
|
||||
|
||||
async function cancelSentInvitation(invitationId: string) {
|
||||
return organization.cancelInvitation({ invitationId })
|
||||
}
|
||||
|
||||
async function rejectInvitation(invitationId: string) {
|
||||
return organization.rejectInvitation({ invitationId })
|
||||
}
|
||||
|
||||
async function loadOrganizations() {
|
||||
return organization.list()
|
||||
}
|
||||
|
||||
async function checkSlugAvailability(slug: string) {
|
||||
return organization.checkSlug({ slug })
|
||||
}
|
||||
|
||||
async function setActiveOrganization(organizationId: string) {
|
||||
return organization.setActive({ organizationId })
|
||||
}
|
||||
|
||||
return {
|
||||
createOrganization,
|
||||
deleteOrganization,
|
||||
getInvitation,
|
||||
listInvitations,
|
||||
inviteMember,
|
||||
acceptInvitation,
|
||||
cancelSentInvitation,
|
||||
rejectInvitation,
|
||||
loadOrganizations,
|
||||
checkSlugAvailability,
|
||||
setActiveOrganization
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import { createAuthClient } from 'better-auth/vue'
|
||||
import type { InferSessionFromClient, InferUserFromClient, ClientOptions } from 'better-auth/client'
|
||||
import { organizationClient, jwtClient } from 'better-auth/client/plugins'
|
||||
import type { RouteLocationRaw } from 'vue-router'
|
||||
import type { UserDto } from '~/.api-client'
|
||||
import type { RouteLocationNormalizedLoaded } from '#vue-router'
|
||||
import {
|
||||
accessControl,
|
||||
@@ -22,6 +21,24 @@ interface RuntimeAuthConfig {
|
||||
redirectGuestTo: RouteLocationRaw | string
|
||||
}
|
||||
|
||||
// Types can be found here: https://github.com/better-auth/better-auth/blob/3f574ec70bb15c155a78673d42c5e25f7376ced3/packages/better-auth/src/plugins/organization/routes/crud-invites.ts#L531
|
||||
type Client = ReturnType<typeof useAuth>['client']
|
||||
|
||||
export type Session = Client['$Infer']['Session']
|
||||
export type User = Session['user']
|
||||
export type ActiveOrganization = Client['$Infer']['ActiveOrganization']
|
||||
export type Organization = Client['$Infer']['Organization']
|
||||
export type Invitation = Client['$Infer']['Invitation']
|
||||
|
||||
// Extended invitation type with additional organization and inviter details
|
||||
export type CustomInvitation =
|
||||
| (Invitation & {
|
||||
organizationName: string
|
||||
organizationSlug: string
|
||||
inviterEmail: string
|
||||
})
|
||||
| null
|
||||
|
||||
// TODO: Move into pinia store
|
||||
const session = ref<InferSessionFromClient<ClientOptions> | null>(null)
|
||||
const user = ref<InferUserFromClient<ClientOptions> | null>(null)
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import type { ActiveOrganization } from '~/types/betterAuth'
|
||||
import type { LegalRole } from '~/server/utils/permissions'
|
||||
|
||||
const activeOrganization = ref<ActiveOrganization | null>(null)
|
||||
const selectedOrgId = ref<string | undefined>(undefined)
|
||||
|
||||
export function useBetterAuth() {
|
||||
const toast = useToast()
|
||||
const { organization } = useAuth()
|
||||
|
||||
async function createOrganization(name: string, slug: string, logo?: string) {
|
||||
const slugCheck = await organization.checkSlug({ slug })
|
||||
if (!slugCheck.data?.status) {
|
||||
toast.add({ title: 'Slug bereits vergeben', description: 'Bitte wählen Sie einen anderen Slug', color: 'error' })
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
return await organization.create(
|
||||
{ name, slug, logo },
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.add({ title: 'Organisation erfolgreich erstellt', color: 'success' })
|
||||
return Promise.resolve()
|
||||
},
|
||||
onError: (ctx) => {
|
||||
toast.add({
|
||||
title: 'Fehler bei der Erstellung der Organisation',
|
||||
description: ctx.error.message,
|
||||
color: 'error'
|
||||
})
|
||||
return Promise.reject()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function deleteOrganization() {
|
||||
await organization.delete(
|
||||
{ organizationId: activeOrganization.value?.id ?? '' },
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.add({ title: 'Organization deleted', color: 'success' })
|
||||
activeOrganization.value = null
|
||||
selectedOrgId.value = undefined
|
||||
},
|
||||
onError: (ctx) => {
|
||||
toast.add({ title: 'Error deleting organization', description: ctx.error.message, color: 'error' })
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
async function getInvitation(invitationId: string): Promise<CustomInvitation> {
|
||||
return organization.getInvitation({
|
||||
query: { id: invitationId },
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: (ctx) => {
|
||||
return ctx.data
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.add({ title: 'Fehler beim Einladen', description: error.error.message, color: 'error' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function inviteMember(email: string, role: LegalRole) {
|
||||
await organization.inviteMember({
|
||||
email,
|
||||
role,
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: (ctx) => {
|
||||
if (activeOrganization.value) {
|
||||
activeOrganization.value = {
|
||||
...activeOrganization.value,
|
||||
invitations: [...(activeOrganization.value?.invitations || []), ctx.data]
|
||||
}
|
||||
}
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.add({ title: 'Fehler beim Einladen', description: error.error.message, color: 'error' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function acceptInvitation(invitationId: string) {
|
||||
await organization.acceptInvitation({
|
||||
invitationId,
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: async () => {
|
||||
toast.add({ title: 'Invitation accepted', color: 'success' })
|
||||
await navigateTo('/')
|
||||
},
|
||||
onError: (ctx) => {
|
||||
toast.add({ title: 'Error when accepting invitation', description: ctx.error.message, color: 'error' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function rejectInvitation(invitationId: string) {
|
||||
await organization.rejectInvitation({
|
||||
invitationId,
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: () => {
|
||||
toast.add({ title: 'Invitation rejected', color: 'success' })
|
||||
},
|
||||
onError: (ctx) => {
|
||||
toast.add({ title: 'Error when rejecting invitation', description: ctx.error.message, color: 'error' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
activeOrganization,
|
||||
selectedOrgId,
|
||||
createOrganization,
|
||||
deleteOrganization,
|
||||
getInvitation,
|
||||
inviteMember,
|
||||
acceptInvitation,
|
||||
rejectInvitation
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: new Database('./sqlite.db'),
|
||||
onAPIError: { throw: true },
|
||||
emailAndPassword: { enabled: true, autoSignIn: false },
|
||||
trustedOrigins: ['http://localhost:3001'],
|
||||
plugins: [
|
||||
|
||||
@@ -2,7 +2,7 @@ import { defineStore } from 'pinia'
|
||||
import { type CreateCommentDto, type CommentDto, ResponseError } from '~/.api-client'
|
||||
import { useCommentApi } from '~/composables/comment/useCommentApi'
|
||||
|
||||
export const useCommentStore = defineStore('comment', () => {
|
||||
export const useCommentStore = defineStore('Comment', () => {
|
||||
type FormElementId = string
|
||||
const commentApi = useCommentApi()
|
||||
const comments = ref<Record<FormElementId, CommentDto[]>>({})
|
||||
|
||||
160
legalconsenthub/stores/useOrganizationStore.ts
Normal file
160
legalconsenthub/stores/useOrganizationStore.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import type { ActiveOrganization, Organization, Invitation } from '~/composables/useAuth'
|
||||
import { useOrganizationApi } from '~/composables/organization/useOrganizationApi'
|
||||
import type { LegalRole } from '~/server/utils/permissions'
|
||||
|
||||
export const useOrganizationStore = defineStore('Organization', () => {
|
||||
const activeOrganization = ref<ActiveOrganization | null>(null)
|
||||
const organizations = ref<Organization[]>([])
|
||||
const invitations = ref<Invitation[]>([])
|
||||
|
||||
const organizationApi = useOrganizationApi()
|
||||
const toast = useToast()
|
||||
|
||||
async function createOrganization(name: string, slug: string, logo?: string) {
|
||||
try {
|
||||
const { data: slugCheck } = await organizationApi.checkSlugAvailability(slug)
|
||||
if (!slugCheck?.status) {
|
||||
toast.add({
|
||||
title: 'Slug already taken',
|
||||
description: 'Please choose a different slug',
|
||||
color: 'error'
|
||||
})
|
||||
return Promise.reject()
|
||||
}
|
||||
|
||||
const { data: createdOrganization } = await organizationApi.createOrganization(name, slug, logo)
|
||||
if (createdOrganization) {
|
||||
organizations.value.push(createdOrganization)
|
||||
toast.add({ title: 'Organization created successfully', color: 'success' })
|
||||
|
||||
if (createdOrganization.id) {
|
||||
await setActiveOrganization(createdOrganization.id)
|
||||
}
|
||||
|
||||
return createdOrganization
|
||||
}
|
||||
} catch (e) {
|
||||
toast.add({ title: 'Error creating organization', color: 'error' })
|
||||
console.error('Error creating organization:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteOrganization(organizationId?: string) {
|
||||
try {
|
||||
const idToDelete = organizationId ?? activeOrganization.value?.id
|
||||
if (!idToDelete) {
|
||||
throw Error(`No organization is selected for deletion`)
|
||||
}
|
||||
await organizationApi.deleteOrganization(idToDelete)
|
||||
organizations.value = organizations.value.filter((org) => org.id !== organizationId)
|
||||
toast.add({ title: 'Organization deleted successfully', color: 'success' })
|
||||
} catch (e) {
|
||||
toast.add({ title: 'Error deleting organization', color: 'error' })
|
||||
console.error('Error deleting organization:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function getInvitation(invitationId: string): Promise<CustomInvitation> {
|
||||
try {
|
||||
const { data: invitation } = await organizationApi.getInvitation(invitationId)
|
||||
return invitation
|
||||
} catch (e) {
|
||||
console.error('Error getInvitation:', e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function loadInvitations(organizationId?: string) {
|
||||
try {
|
||||
const { data: loadedInvitations } = await organizationApi.listInvitations(organizationId)
|
||||
if (loadedInvitations) {
|
||||
invitations.value = loadedInvitations
|
||||
}
|
||||
} catch (e) {
|
||||
toast.add({ title: 'Error loading invitations', color: 'error' })
|
||||
console.error('Error loading invitations:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function inviteMember(email: string, role: LegalRole) {
|
||||
try {
|
||||
await organizationApi.inviteMember(email, role)
|
||||
await loadInvitations()
|
||||
toast.add({ title: 'Member invited successfully', color: 'success' })
|
||||
} catch (e) {
|
||||
toast.add({ title: 'Error inviting member', color: 'error' })
|
||||
console.error('Error inviting member:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function acceptInvitation(invitationId: string) {
|
||||
try {
|
||||
await organizationApi.acceptInvitation(invitationId)
|
||||
await navigateTo('/')
|
||||
} catch (e) {
|
||||
toast.add({ title: 'Error accepting invitation', color: 'error' })
|
||||
console.error('Error accepting invitation:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function cancelSentInvitation(invitationId: string) {
|
||||
try {
|
||||
await organizationApi.cancelSentInvitation(invitationId)
|
||||
invitations.value = invitations.value.filter((invitation) => invitation.id !== invitationId)
|
||||
} catch (e) {
|
||||
toast.add({ title: 'Error rejecting invitation', color: 'error' })
|
||||
console.error('Error rejecting invitation:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function rejectInvitation(invitationId: string) {
|
||||
try {
|
||||
await organizationApi.rejectInvitation(invitationId)
|
||||
invitations.value = invitations.value.filter((invitation) => invitation.id !== invitationId)
|
||||
} catch (e) {
|
||||
toast.add({ title: 'Error rejecting invitation', color: 'error' })
|
||||
console.error('Error rejecting invitation:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function loadOrganizations() {
|
||||
try {
|
||||
const { data: loadedOrganizations } = await organizationApi.loadOrganizations()
|
||||
if (loadedOrganizations) {
|
||||
organizations.value = loadedOrganizations
|
||||
}
|
||||
} catch (e) {
|
||||
toast.add({ title: 'Error loading organizations', color: 'error' })
|
||||
console.error('Error loading organizations:', e)
|
||||
}
|
||||
}
|
||||
|
||||
async function setActiveOrganization(organizationId: string) {
|
||||
try {
|
||||
const { data: activeOrganizationToSet } = await organizationApi.setActiveOrganization(organizationId)
|
||||
activeOrganization.value = activeOrganizationToSet
|
||||
|
||||
const { data: invitationsToSet } = await organizationApi.listInvitations(activeOrganizationToSet?.id)
|
||||
invitations.value = invitationsToSet ?? []
|
||||
} catch (e) {
|
||||
toast.add({ title: 'Error setting active organizations', color: 'error' })
|
||||
console.error('Error setting active organizations:', e)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
activeOrganization,
|
||||
organizations,
|
||||
createOrganization,
|
||||
deleteOrganization,
|
||||
invitations,
|
||||
getInvitation,
|
||||
loadInvitations,
|
||||
inviteMember,
|
||||
acceptInvitation,
|
||||
rejectInvitation,
|
||||
cancelSentInvitation,
|
||||
loadOrganizations,
|
||||
setActiveOrganization
|
||||
}
|
||||
})
|
||||
@@ -1,4 +0,0 @@
|
||||
import type { useAuth } from '#imports'
|
||||
|
||||
type Client = ReturnType<typeof useAuth>['client']
|
||||
export type ActiveOrganization = Client['$Infer']['ActiveOrganization']
|
||||
@@ -1,13 +0,0 @@
|
||||
// Types can be found here: https://github.com/better-auth/better-auth/blob/3f574ec70bb15c155a78673d42c5e25f7376ced3/packages/better-auth/src/plugins/organization/routes/crud-invites.ts#L531
|
||||
export type CustomInvitation = {
|
||||
organizationName: string
|
||||
organizationSlug: string
|
||||
inviterEmail: string
|
||||
id: string
|
||||
status: 'pending' | 'accepted' | 'rejected' | 'canceled'
|
||||
email: string
|
||||
expiresAt: Date
|
||||
organizationId: string
|
||||
role: string
|
||||
inviterId: string
|
||||
} | null
|
||||
Reference in New Issue
Block a user