feat(frontend): Add ogranization invitation
This commit is contained in:
@@ -29,11 +29,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const props = defineProps<{
|
||||
defineProps<{
|
||||
organization: ActiveOrganization
|
||||
}>()
|
||||
|
||||
const emit = defineEmits(['update'])
|
||||
const { inviteMember } = useBetterAuth()
|
||||
|
||||
const open = ref(false)
|
||||
const loading = ref(false)
|
||||
@@ -56,27 +56,9 @@ watch(open, (val) => {
|
||||
|
||||
async function handleSubmit() {
|
||||
loading.value = true
|
||||
await organization.inviteMember({
|
||||
email: form.value.email,
|
||||
role: form.value.role as 'member' | 'admin',
|
||||
fetchOptions: {
|
||||
throw: true,
|
||||
onSuccess: (ctx) => {
|
||||
const updatedOrg = {
|
||||
...props.organization,
|
||||
invitations: [...(props.organization.invitations || []), ctx.data]
|
||||
}
|
||||
emit('update', updatedOrg)
|
||||
open.value = false
|
||||
},
|
||||
onError: (error) => {
|
||||
useToast().add({ title: 'Fehler beim Einladen', description: error.error.message, color: 'error' })
|
||||
},
|
||||
onResponse: () => {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
})
|
||||
await inviteMember(form.value.email, form.value.role as 'member' | 'admin')
|
||||
loading.value = false
|
||||
open.value = false
|
||||
useToast().add({ title: 'Invitation sent', color: 'success' })
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -40,10 +40,81 @@ export function useBetterAuth() {
|
||||
)
|
||||
}
|
||||
|
||||
async function getInvitation(invitationId: string): Promise<CustomInvitation> {
|
||||
return authClient.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: 'member' | 'admin') {
|
||||
await authClient.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 authClient.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 authClient.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
|
||||
deleteOrganization,
|
||||
getInvitation,
|
||||
inviteMember,
|
||||
acceptInvitation,
|
||||
rejectInvitation
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "nuxt build",
|
||||
"dev": "nuxt dev",
|
||||
"dev": "nuxt dev --port 3001",
|
||||
"generate": "nuxt generate",
|
||||
"preview": "nuxt preview",
|
||||
"postinstall": "nuxt prepare && pnpm run fix:bettersqlite",
|
||||
@@ -22,6 +22,7 @@
|
||||
"better-sqlite3": "11.8.1",
|
||||
"nuxt": "3.16.1",
|
||||
"pinia": "3.0.1",
|
||||
"resend": "^4.3.0",
|
||||
"vue": "latest",
|
||||
"vue-router": "latest"
|
||||
},
|
||||
|
||||
90
legalconsenthub/pages/accept-invitation/[id].vue
Normal file
90
legalconsenthub/pages/accept-invitation/[id].vue
Normal file
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<UDashboardPanel>
|
||||
<template #header>
|
||||
<UDashboardNavbar title="Accept Invitation" :ui="{ right: 'gap-3' }">
|
||||
<template #leading>
|
||||
<UDashboardSidebarCollapse />
|
||||
</template>
|
||||
|
||||
<template #right />
|
||||
</UDashboardNavbar>
|
||||
|
||||
<UDashboardToolbar>
|
||||
<template #left />
|
||||
</UDashboardToolbar>
|
||||
</template>
|
||||
|
||||
<template #body>
|
||||
<div class="flex flex-col gap-4 sm:gap-6 lg:gap-12 w-full lg:max-w-2xl mx-auto">
|
||||
<UPageCard title="Accept Invitation" description="Accept or decline the invitation." class="mb-6">
|
||||
<div v-if="invitation && !error">
|
||||
<div v-if="invitationStatus === 'pending'" class="space-y-4">
|
||||
<p>
|
||||
<strong>{{ invitation.inviterEmail }}</strong> has invited you to join
|
||||
<strong>{{ invitation.organizationName }}</strong
|
||||
>.
|
||||
</p>
|
||||
<p>
|
||||
This invitation was sent to <strong>{{ invitation.email }}</strong
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div v-if="invitationStatus === 'accepted'" class="space-y-4 text-center">
|
||||
<div class="flex items-center justify-center w-16 h-16 mx-auto bg-green-100 rounded-full">
|
||||
<UIcon name="i-lucide-check" class="w-8 h-8 text-green-600" />
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold">Welcome to {{ invitation.organizationName }}!</h2>
|
||||
<p>You've successfully joined the organization. We're excited to have you on board!</p>
|
||||
</div>
|
||||
|
||||
<div v-if="invitationStatus === 'rejected'" class="space-y-4 text-center">
|
||||
<div class="flex items-center justify-center w-16 h-16 mx-auto bg-red-100 rounded-full">
|
||||
<UIcon name="i-lucide-x" class="w-8 h-8 text-red-600" />
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold">Invitation Declined</h2>
|
||||
<p>You've declined the invitation to join {{ invitation.organizationName }}.</p>
|
||||
</div>
|
||||
|
||||
<div v-if="invitationStatus === 'pending'" class="flex justify-between mt-6">
|
||||
<UButton variant="soft" color="neutral" @click="handleReject">Decline</UButton>
|
||||
<UButton color="primary" @click="handleAccept">Accept Invitation</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-else-if="!invitation && !error" class="space-y-4">
|
||||
<USkeleton class="w-1/3 h-6" />
|
||||
<USkeleton class="w-full h-4" />
|
||||
<USkeleton class="w-2/3 h-4" />
|
||||
<USkeleton class="w-24 h-10 ml-auto" />
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<p class="text-red-600 font-medium">{{ error }}</p>
|
||||
</div>
|
||||
</UPageCard>
|
||||
</div>
|
||||
</template>
|
||||
</UDashboardPanel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const invitationId = useRoute().params.id as string
|
||||
|
||||
const { acceptInvitation, rejectInvitation, getInvitation } = useBetterAuth()
|
||||
const invitation = ref<CustomInvitation>(null)
|
||||
const invitationStatus = ref<'pending' | 'accepted' | 'rejected'>('pending')
|
||||
const error = ref<string | null>(null)
|
||||
|
||||
async function handleAccept() {
|
||||
await acceptInvitation(invitationId)
|
||||
}
|
||||
|
||||
async function handleReject() {
|
||||
await rejectInvitation(invitationId)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
invitation.value = await getInvitation(invitationId)
|
||||
})
|
||||
</script>
|
||||
124
legalconsenthub/pnpm-lock.yaml
generated
124
legalconsenthub/pnpm-lock.yaml
generated
@@ -26,6 +26,9 @@ importers:
|
||||
pinia:
|
||||
specifier: 3.0.1
|
||||
version: 3.0.1(typescript@5.7.3)(vue@3.5.13(typescript@5.7.3))
|
||||
resend:
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
vue:
|
||||
specifier: latest
|
||||
version: 3.5.13(typescript@5.7.3)
|
||||
@@ -933,6 +936,13 @@ packages:
|
||||
resolution: {integrity: sha512-aQypoot0HPSJa6gDPEPTntc1GT6QINrSbgRlRhadGW2WaYqUK3tK4Bw9SBMZXhmxd3GeAlZjVcODHgiu+THY7A==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@react-email/render@1.0.6':
|
||||
resolution: {integrity: sha512-zNueW5Wn/4jNC1c5LFgXzbUdv5Lhms+FWjOvWAhal7gx5YVf0q6dPJ0dnR70+ifo59gcMLwCZEaTS9EEuUhKvQ==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
peerDependencies:
|
||||
react: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
react-dom: ^18.0 || ^19.0 || ^19.0.0-rc
|
||||
|
||||
'@rollup/plugin-alias@5.1.1':
|
||||
resolution: {integrity: sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
@@ -1105,6 +1115,9 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@selderee/plugin-htmlparser2@0.11.0':
|
||||
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
|
||||
|
||||
'@simplewebauthn/browser@13.1.0':
|
||||
resolution: {integrity: sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==}
|
||||
|
||||
@@ -2509,6 +2522,9 @@ packages:
|
||||
externality@1.0.2:
|
||||
resolution: {integrity: sha512-LyExtJWKxtgVzmgtEHyQtLFpw1KFhQphF9nTG8TpAIVkiI/xQ3FJh75tRFLYl4hkn7BNIIdLJInuDAavX35pMw==}
|
||||
|
||||
fast-deep-equal@2.0.1:
|
||||
resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==}
|
||||
|
||||
fast-deep-equal@3.1.3:
|
||||
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
|
||||
|
||||
@@ -2755,6 +2771,13 @@ packages:
|
||||
resolution: {integrity: sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==}
|
||||
engines: {node: ^16.14.0 || >=18.0.0}
|
||||
|
||||
html-to-text@9.0.5:
|
||||
resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
htmlparser2@8.0.2:
|
||||
resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
|
||||
|
||||
http-errors@2.0.0:
|
||||
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -3035,6 +3058,9 @@ packages:
|
||||
resolution: {integrity: sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==}
|
||||
engines: {node: '>= 0.6.3'}
|
||||
|
||||
leac@0.6.0:
|
||||
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
|
||||
|
||||
levn@0.4.1:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
@@ -3525,6 +3551,9 @@ packages:
|
||||
resolution: {integrity: sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==}
|
||||
engines: {node: '>=14.13.0'}
|
||||
|
||||
parseley@0.12.1:
|
||||
resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
|
||||
|
||||
parseurl@1.3.3:
|
||||
resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==}
|
||||
engines: {node: '>= 0.8'}
|
||||
@@ -3568,6 +3597,9 @@ packages:
|
||||
pathe@2.0.3:
|
||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||
|
||||
peberminta@0.9.0:
|
||||
resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
|
||||
|
||||
perfect-debounce@1.0.0:
|
||||
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
|
||||
|
||||
@@ -3792,6 +3824,11 @@ packages:
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
prettier@3.5.3:
|
||||
resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
|
||||
engines: {node: '>=14'}
|
||||
hasBin: true
|
||||
|
||||
pretty-bytes@6.1.1:
|
||||
resolution: {integrity: sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==}
|
||||
engines: {node: ^14.13.1 || >=16.0.0}
|
||||
@@ -3854,6 +3891,18 @@ packages:
|
||||
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
|
||||
hasBin: true
|
||||
|
||||
react-dom@19.1.0:
|
||||
resolution: {integrity: sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==}
|
||||
peerDependencies:
|
||||
react: ^19.1.0
|
||||
|
||||
react-promise-suspense@0.3.4:
|
||||
resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==}
|
||||
|
||||
react@19.1.0:
|
||||
resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
read-package-up@11.0.0:
|
||||
resolution: {integrity: sha512-MbgfoNPANMdb4oRBNg5eqLbB2t2r+o5Ua1pNt8BqGp4I0FJZhuVSOj3PaBPni4azWuSzEdNn2evevzVmEk1ohQ==}
|
||||
engines: {node: '>=18'}
|
||||
@@ -3923,6 +3972,10 @@ packages:
|
||||
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
resend@4.3.0:
|
||||
resolution: {integrity: sha512-4OBHeusMVSl0vcba2J3AaGzdZ1SXAAhX/Wkcwobe16AHmlW9h3li8wG62Fhvlsc61e+wlQoxcwJZP6WrBTbghQ==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
resolve-from@4.0.0:
|
||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||
engines: {node: '>=4'}
|
||||
@@ -4005,6 +4058,9 @@ packages:
|
||||
safer-buffer@2.1.2:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
|
||||
scheduler@0.26.0:
|
||||
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
|
||||
|
||||
scslre@0.3.0:
|
||||
resolution: {integrity: sha512-3A6sD0WYP7+QrjbfNA2FN3FsOaGGFoekCVgTyypy53gPxhbkCIjtO6YWgdrfM+n/8sI8JeXZOIxsHjMTNxQ4nQ==}
|
||||
engines: {node: ^14.0.0 || >=16.0.0}
|
||||
@@ -4012,6 +4068,9 @@ packages:
|
||||
scule@1.3.0:
|
||||
resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==}
|
||||
|
||||
selderee@0.11.0:
|
||||
resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==}
|
||||
|
||||
semver@6.3.1:
|
||||
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
|
||||
hasBin: true
|
||||
@@ -6099,6 +6158,14 @@ snapshots:
|
||||
|
||||
'@poppinss/exception@1.2.1': {}
|
||||
|
||||
'@react-email/render@1.0.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)':
|
||||
dependencies:
|
||||
html-to-text: 9.0.5
|
||||
prettier: 3.5.3
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
react-promise-suspense: 0.3.4
|
||||
|
||||
'@rollup/plugin-alias@5.1.1(rollup@4.38.0)':
|
||||
optionalDependencies:
|
||||
rollup: 4.38.0
|
||||
@@ -6222,6 +6289,11 @@ snapshots:
|
||||
'@rollup/rollup-win32-x64-msvc@4.38.0':
|
||||
optional: true
|
||||
|
||||
'@selderee/plugin-htmlparser2@0.11.0':
|
||||
dependencies:
|
||||
domhandler: 5.0.3
|
||||
selderee: 0.11.0
|
||||
|
||||
'@simplewebauthn/browser@13.1.0': {}
|
||||
|
||||
'@simplewebauthn/server@13.1.1':
|
||||
@@ -7750,6 +7822,8 @@ snapshots:
|
||||
pathe: 1.1.2
|
||||
ufo: 1.5.4
|
||||
|
||||
fast-deep-equal@2.0.1: {}
|
||||
|
||||
fast-deep-equal@3.1.3: {}
|
||||
|
||||
fast-fifo@1.3.2: {}
|
||||
@@ -8018,6 +8092,21 @@ snapshots:
|
||||
dependencies:
|
||||
lru-cache: 10.4.3
|
||||
|
||||
html-to-text@9.0.5:
|
||||
dependencies:
|
||||
'@selderee/plugin-htmlparser2': 0.11.0
|
||||
deepmerge: 4.3.1
|
||||
dom-serializer: 2.0.0
|
||||
htmlparser2: 8.0.2
|
||||
selderee: 0.11.0
|
||||
|
||||
htmlparser2@8.0.2:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
domhandler: 5.0.3
|
||||
domutils: 3.2.2
|
||||
entities: 4.5.0
|
||||
|
||||
http-errors@2.0.0:
|
||||
dependencies:
|
||||
depd: 2.0.0
|
||||
@@ -8271,6 +8360,8 @@ snapshots:
|
||||
dependencies:
|
||||
readable-stream: 2.3.8
|
||||
|
||||
leac@0.6.0: {}
|
||||
|
||||
levn@0.4.1:
|
||||
dependencies:
|
||||
prelude-ls: 1.2.1
|
||||
@@ -8939,6 +9030,11 @@ snapshots:
|
||||
'@types/parse-path': 7.0.3
|
||||
parse-path: 7.0.1
|
||||
|
||||
parseley@0.12.1:
|
||||
dependencies:
|
||||
leac: 0.6.0
|
||||
peberminta: 0.9.0
|
||||
|
||||
parseurl@1.3.3: {}
|
||||
|
||||
path-browserify@1.0.1: {}
|
||||
@@ -8966,6 +9062,8 @@ snapshots:
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
peberminta@0.9.0: {}
|
||||
|
||||
perfect-debounce@1.0.0: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
@@ -9181,6 +9279,8 @@ snapshots:
|
||||
|
||||
prettier@3.5.1: {}
|
||||
|
||||
prettier@3.5.3: {}
|
||||
|
||||
pretty-bytes@6.1.1: {}
|
||||
|
||||
process-nextick-args@2.0.1: {}
|
||||
@@ -9246,6 +9346,17 @@ snapshots:
|
||||
minimist: 1.2.8
|
||||
strip-json-comments: 2.0.1
|
||||
|
||||
react-dom@19.1.0(react@19.1.0):
|
||||
dependencies:
|
||||
react: 19.1.0
|
||||
scheduler: 0.26.0
|
||||
|
||||
react-promise-suspense@0.3.4:
|
||||
dependencies:
|
||||
fast-deep-equal: 2.0.1
|
||||
|
||||
react@19.1.0: {}
|
||||
|
||||
read-package-up@11.0.0:
|
||||
dependencies:
|
||||
find-up-simple: 1.0.1
|
||||
@@ -9338,6 +9449,13 @@ snapshots:
|
||||
|
||||
require-directory@2.1.1: {}
|
||||
|
||||
resend@4.3.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0):
|
||||
dependencies:
|
||||
'@react-email/render': 1.0.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
transitivePeerDependencies:
|
||||
- react
|
||||
- react-dom
|
||||
|
||||
resolve-from@4.0.0: {}
|
||||
|
||||
resolve-from@5.0.0: {}
|
||||
@@ -9424,6 +9542,8 @@ snapshots:
|
||||
|
||||
safer-buffer@2.1.2: {}
|
||||
|
||||
scheduler@0.26.0: {}
|
||||
|
||||
scslre@0.3.0:
|
||||
dependencies:
|
||||
'@eslint-community/regexpp': 4.12.1
|
||||
@@ -9432,6 +9552,10 @@ snapshots:
|
||||
|
||||
scule@1.3.0: {}
|
||||
|
||||
selderee@0.11.0:
|
||||
dependencies:
|
||||
parseley: 0.12.1
|
||||
|
||||
semver@6.3.1: {}
|
||||
|
||||
semver@7.7.1: {}
|
||||
|
||||
@@ -1,9 +1,23 @@
|
||||
import { betterAuth } from 'better-auth'
|
||||
import Database from 'better-sqlite3'
|
||||
import { organization } from 'better-auth/plugins'
|
||||
import { resend } from './mail'
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: new Database('./sqlite.db'),
|
||||
emailAndPassword: { enabled: true, autoSignIn: false },
|
||||
plugins: [organization()]
|
||||
plugins: [
|
||||
organization({
|
||||
async sendInvitationEmail(data) {
|
||||
console.log('Sending invitation email', data)
|
||||
const inviteLink = `http://localhost:3001/accept-invitation/${data.id}`
|
||||
await resend.emails.send({
|
||||
from: 'Acme <onboarding@resend.dev>',
|
||||
to: data.email,
|
||||
subject: 'Email Verification',
|
||||
html: `You are invited to the Organization ${data.organization.name}! Click the link to verify your email: ${inviteLink}`
|
||||
})
|
||||
}
|
||||
})
|
||||
]
|
||||
})
|
||||
|
||||
3
legalconsenthub/server/utils/mail.ts
Normal file
3
legalconsenthub/server/utils/mail.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import { Resend } from 'resend'
|
||||
|
||||
export const resend = new Resend(process.env.RESEND_API_KEY)
|
||||
@@ -4,3 +4,17 @@ import type { authClient } from './auth-client'
|
||||
export type Session = typeof auth.$Infer.Session
|
||||
export type ActiveOrganization = typeof authClient.$Infer.ActiveOrganization
|
||||
export type Invitation = typeof authClient.$Infer.Invitation
|
||||
|
||||
// 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