feat(frontend): Refactor auth and split into separate files
This commit is contained in:
@@ -1,12 +1,29 @@
|
|||||||
import type { FormSubmitEvent } from '@nuxt/ui'
|
import type { FormSubmitEvent } from '@nuxt/ui'
|
||||||
import { UserRole } from '~/.api-client'
|
import { UserRole } from '~/.api-client'
|
||||||
import type { SignInSchema, SignUpSchema } from '~/types/schemas'
|
import type { SignInSchema, SignUpSchema } from '~/types/schemas'
|
||||||
|
import type { RouteLocationRaw } from 'vue-router'
|
||||||
|
import { useAuthClient } from '~/composables/auth/useAuthClient'
|
||||||
|
import { useAuthState } from '~/composables/auth/useAuthState'
|
||||||
|
|
||||||
export const useUserStore = defineStore('User', () => {
|
export function useAuthActions() {
|
||||||
|
const { client } = useAuthClient()
|
||||||
|
const { session, user } = useAuthState()
|
||||||
const { createUser } = useUser()
|
const { createUser } = useUser()
|
||||||
const name = ref('')
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const { client } = useAuth()
|
|
||||||
|
async function signOut({ redirectTo }: { redirectTo?: RouteLocationRaw } = {}) {
|
||||||
|
const res = await client.signOut()
|
||||||
|
if (res.error) {
|
||||||
|
console.error('Error signing out:', res.error)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
session.value = null
|
||||||
|
user.value = null
|
||||||
|
if (redirectTo) {
|
||||||
|
await navigateTo(redirectTo, { external: true })
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
async function signUp(payload: FormSubmitEvent<SignUpSchema>) {
|
async function signUp(payload: FormSubmitEvent<SignUpSchema>) {
|
||||||
await client.signUp.email(
|
await client.signUp.email(
|
||||||
@@ -48,7 +65,7 @@ export const useUserStore = defineStore('User', () => {
|
|||||||
},
|
},
|
||||||
onError: async (ctx) => {
|
onError: async (ctx) => {
|
||||||
console.log(ctx.error.message)
|
console.log(ctx.error.message)
|
||||||
useToast().add({
|
toast.add({
|
||||||
title: 'Fehler bei der Registrierung',
|
title: 'Fehler bei der Registrierung',
|
||||||
description: ctx.error.message,
|
description: ctx.error.message,
|
||||||
color: 'error'
|
color: 'error'
|
||||||
@@ -68,15 +85,25 @@ export const useUserStore = defineStore('User', () => {
|
|||||||
onRequest: () => {
|
onRequest: () => {
|
||||||
console.log('Sending login request')
|
console.log('Sending login request')
|
||||||
},
|
},
|
||||||
onSuccess: () => {
|
onSuccess: async () => {
|
||||||
console.log('Successfully logged in!')
|
console.log('Successfully logged in!')
|
||||||
|
await navigateTo('/')
|
||||||
},
|
},
|
||||||
onError: (ctx) => {
|
onError: (ctx) => {
|
||||||
console.log(ctx.error.message)
|
console.log(ctx.error.message)
|
||||||
|
toast.add({
|
||||||
|
title: 'Fehler bei der Anmeldung',
|
||||||
|
description: ctx.error.message,
|
||||||
|
color: 'error'
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return { name, signUp, signIn }
|
return {
|
||||||
})
|
signOut,
|
||||||
|
signUp,
|
||||||
|
signIn
|
||||||
|
}
|
||||||
|
}
|
||||||
46
legalconsenthub/composables/auth/useAuthClient.ts
Normal file
46
legalconsenthub/composables/auth/useAuthClient.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import { createAuthClient } from 'better-auth/vue'
|
||||||
|
import { jwtClient, organizationClient } from 'better-auth/client/plugins'
|
||||||
|
import {
|
||||||
|
accessControl,
|
||||||
|
adminRole,
|
||||||
|
employeeRole,
|
||||||
|
employerRole,
|
||||||
|
ownerRole,
|
||||||
|
ROLES,
|
||||||
|
worksCouncilMemberRole
|
||||||
|
} from '~/server/utils/permissions'
|
||||||
|
|
||||||
|
export function useAuthClient() {
|
||||||
|
const url = useRequestURL()
|
||||||
|
const headers = import.meta.server ? useRequestHeaders() : undefined
|
||||||
|
|
||||||
|
const client = createAuthClient({
|
||||||
|
baseURL: url.origin,
|
||||||
|
fetchOptions: {
|
||||||
|
headers
|
||||||
|
},
|
||||||
|
user: {
|
||||||
|
deleteUser: {
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
organizationClient({
|
||||||
|
// Pass the same access control instance and roles to client
|
||||||
|
ac: accessControl,
|
||||||
|
roles: {
|
||||||
|
[ROLES.EMPLOYER]: employerRole,
|
||||||
|
[ROLES.WORKS_COUNCIL_MEMBER]: worksCouncilMemberRole,
|
||||||
|
[ROLES.EMPLOYEE]: employeeRole,
|
||||||
|
[ROLES.ADMIN]: adminRole,
|
||||||
|
[ROLES.OWNER]: ownerRole
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
jwtClient()
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
client
|
||||||
|
}
|
||||||
|
}
|
||||||
130
legalconsenthub/composables/auth/useAuthState.ts
Normal file
130
legalconsenthub/composables/auth/useAuthState.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import type { ClientOptions, InferSessionFromClient, InferUserFromClient } from 'better-auth/client'
|
||||||
|
import type { RuntimeAuthConfig } from '~/types/auth'
|
||||||
|
import { defu } from 'defu'
|
||||||
|
import { useAuthClient } from '~/composables/auth/useAuthClient'
|
||||||
|
|
||||||
|
// Global state for auth
|
||||||
|
const session = ref<InferSessionFromClient<ClientOptions> | null>(null)
|
||||||
|
const user = ref<InferUserFromClient<ClientOptions> | null>(null)
|
||||||
|
const sessionFetching = import.meta.server ? ref(false) : ref(false)
|
||||||
|
const jwt = ref<string | null>(null)
|
||||||
|
const organizations = ref<
|
||||||
|
{
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
createdAt: Date
|
||||||
|
slug: string
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
logo?: string | null
|
||||||
|
}[]
|
||||||
|
>([])
|
||||||
|
const selectedOrganization = ref<{
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
createdAt: Date
|
||||||
|
slug: string
|
||||||
|
metadata?: Record<string, unknown>
|
||||||
|
logo?: string | null
|
||||||
|
} | null>(null)
|
||||||
|
const activeMember = ref<{ role: string } | null>(null)
|
||||||
|
|
||||||
|
export function useAuthState() {
|
||||||
|
const { client } = useAuthClient()
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const options = defu(useRuntimeConfig().public.auth as Partial<RuntimeAuthConfig>, {
|
||||||
|
redirectUserTo: '/',
|
||||||
|
redirectGuestTo: '/login'
|
||||||
|
})
|
||||||
|
|
||||||
|
const headers = import.meta.server ? useRequestHeaders() : undefined
|
||||||
|
|
||||||
|
async function fetchSession(targetPath?: string) {
|
||||||
|
if (sessionFetching.value) {
|
||||||
|
console.log('already fetching session')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sessionFetching.value = true
|
||||||
|
const { data } = await client.getSession({
|
||||||
|
fetchOptions: {
|
||||||
|
headers
|
||||||
|
}
|
||||||
|
})
|
||||||
|
session.value = data?.session || null
|
||||||
|
user.value = data?.user || null
|
||||||
|
sessionFetching.value = false
|
||||||
|
|
||||||
|
// Only fetch JWT and organizations if we have a session and not on public routes
|
||||||
|
if (session.value && !isPublicPath(targetPath)) {
|
||||||
|
await fetchJwtAndOrganizations()
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchJwtAndOrganizations() {
|
||||||
|
// Fetch JWT
|
||||||
|
const tokenResult = await client.token()
|
||||||
|
jwt.value = tokenResult.data?.token ?? null
|
||||||
|
|
||||||
|
// Fetch organization
|
||||||
|
const orgResult = await client.organization.list({
|
||||||
|
fetchOptions: {
|
||||||
|
headers
|
||||||
|
}
|
||||||
|
})
|
||||||
|
organizations.value = orgResult.data ?? []
|
||||||
|
|
||||||
|
if (!selectedOrganization.value && organizations.value.length > 0) {
|
||||||
|
selectedOrganization.value = organizations.value[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch active member
|
||||||
|
const activeMemberResult = await client.organization.getActiveMember({
|
||||||
|
fetchOptions: {
|
||||||
|
headers
|
||||||
|
}
|
||||||
|
})
|
||||||
|
activeMember.value = activeMemberResult.data || null
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPublicPath(path?: string) {
|
||||||
|
const finalPath = path ?? route.path
|
||||||
|
const publicRoutes = ['/login', '/signup', '/accept-invitation']
|
||||||
|
return publicRoutes.some((path) => finalPath.startsWith(path))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch organization changes
|
||||||
|
watch(
|
||||||
|
() => selectedOrganization.value,
|
||||||
|
async (newValue) => {
|
||||||
|
if (newValue) {
|
||||||
|
await client.organization.setActive({
|
||||||
|
organizationId: newValue?.id
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Client-side session listening
|
||||||
|
if (import.meta.client) {
|
||||||
|
client.$store.listen('$sessionSignal', async (signal) => {
|
||||||
|
if (!signal) return
|
||||||
|
await fetchSession()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
session,
|
||||||
|
user,
|
||||||
|
sessionFetching,
|
||||||
|
jwt,
|
||||||
|
organizations,
|
||||||
|
selectedOrganization,
|
||||||
|
activeMember,
|
||||||
|
options,
|
||||||
|
fetchSession,
|
||||||
|
isPublicPath,
|
||||||
|
loggedIn: computed(() => !!session.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ComplianceStatus, type FormElementDto } from '~/.api-client'
|
import { ComplianceStatus, type FormElementDto } from '~/.api-client'
|
||||||
import { complianceCheckableElementTypes, complianceMap } from './complianceMap'
|
import { complianceCheckableElementTypes, complianceMap } from './complianceMap'
|
||||||
import type { FormElementId } from '~/types/FormElement'
|
import type { FormElementId } from '~/types/formElement'
|
||||||
|
|
||||||
const formElementComplianceMap = ref(new Map<FormElementId, ComplianceStatus>())
|
const formElementComplianceMap = ref(new Map<FormElementId, ComplianceStatus>())
|
||||||
|
|
||||||
|
|||||||
@@ -1,183 +1,19 @@
|
|||||||
// Copied from https://github.com/atinux/nuxthub-better-auth
|
// Copied from https://github.com/atinux/nuxthub-better-auth
|
||||||
|
|
||||||
import { defu } from 'defu'
|
import { useAuthState } from '~/composables/auth/useAuthState'
|
||||||
import { createAuthClient } from 'better-auth/vue'
|
import { useAuthActions } from '~/composables/auth/useAuthActions'
|
||||||
import type { ClientOptions, InferSessionFromClient, InferUserFromClient } from 'better-auth/client'
|
import { useAuthClient } from '~/composables/auth/useAuthClient'
|
||||||
import { jwtClient, organizationClient } from 'better-auth/client/plugins'
|
|
||||||
import type { RouteLocationRaw } from 'vue-router'
|
|
||||||
import {
|
|
||||||
accessControl,
|
|
||||||
adminRole,
|
|
||||||
employeeRole,
|
|
||||||
employerRole,
|
|
||||||
ownerRole,
|
|
||||||
ROLES,
|
|
||||||
worksCouncilMemberRole
|
|
||||||
} from '~/server/utils/permissions'
|
|
||||||
import type { RuntimeAuthConfig } from '~/types/auth'
|
|
||||||
|
|
||||||
const session = ref<InferSessionFromClient<ClientOptions> | null>(null)
|
|
||||||
const user = ref<InferUserFromClient<ClientOptions> | null>(null)
|
|
||||||
const sessionFetching = import.meta.server ? ref(false) : ref(false)
|
|
||||||
const jwt = ref<string | null>(null)
|
|
||||||
const organizations = ref<
|
|
||||||
{
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
createdAt: Date
|
|
||||||
slug: string
|
|
||||||
metadata?: Record<string, unknown>
|
|
||||||
logo?: string | null
|
|
||||||
}[]
|
|
||||||
>([])
|
|
||||||
const selectedOrganization = ref<{
|
|
||||||
id: string
|
|
||||||
name: string
|
|
||||||
createdAt: Date
|
|
||||||
slug: string
|
|
||||||
metadata?: Record<string, unknown>
|
|
||||||
logo?: string | null
|
|
||||||
} | null>(null)
|
|
||||||
const activeMember = ref<{ role: string } | null>(null)
|
|
||||||
|
|
||||||
export function useAuth() {
|
export function useAuth() {
|
||||||
const url = useRequestURL()
|
const authState = useAuthState()
|
||||||
const route = useRoute()
|
const authActions = useAuthActions()
|
||||||
const headers = import.meta.server ? useRequestHeaders() : undefined
|
const { client } = useAuthClient()
|
||||||
|
|
||||||
const client = createAuthClient({
|
|
||||||
baseURL: url.origin,
|
|
||||||
fetchOptions: {
|
|
||||||
headers
|
|
||||||
},
|
|
||||||
user: {
|
|
||||||
deleteUser: {
|
|
||||||
enabled: true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
organizationClient({
|
|
||||||
// Pass the same access control instance and roles to client
|
|
||||||
ac: accessControl,
|
|
||||||
roles: {
|
|
||||||
[ROLES.EMPLOYER]: employerRole,
|
|
||||||
[ROLES.WORKS_COUNCIL_MEMBER]: worksCouncilMemberRole,
|
|
||||||
[ROLES.EMPLOYEE]: employeeRole,
|
|
||||||
[ROLES.ADMIN]: adminRole,
|
|
||||||
[ROLES.OWNER]: ownerRole
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
jwtClient()
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const options = defu(useRuntimeConfig().public.auth as Partial<RuntimeAuthConfig>, {
|
|
||||||
redirectUserTo: '/',
|
|
||||||
redirectGuestTo: '/login'
|
|
||||||
})
|
|
||||||
|
|
||||||
async function fetchSession(targetPath?: string) {
|
|
||||||
if (sessionFetching.value) {
|
|
||||||
console.log('already fetching session')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
sessionFetching.value = true
|
|
||||||
const { data } = await client.getSession({
|
|
||||||
fetchOptions: {
|
|
||||||
headers
|
|
||||||
}
|
|
||||||
})
|
|
||||||
session.value = data?.session || null
|
|
||||||
user.value = data?.user || null
|
|
||||||
sessionFetching.value = false
|
|
||||||
|
|
||||||
// Only fetch JWT and organizations if we have a session and not on public routes
|
|
||||||
if (session.value && !isPublicPath(targetPath)) {
|
|
||||||
await fetchJwtAndOrganizations()
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchJwtAndOrganizations() {
|
|
||||||
// Fetch JWT
|
|
||||||
const tokenResult = await client.token()
|
|
||||||
jwt.value = tokenResult.data?.token ?? null
|
|
||||||
|
|
||||||
// Fetch organization
|
|
||||||
const orgResult = await client.organization.list({
|
|
||||||
fetchOptions: {
|
|
||||||
headers
|
|
||||||
}
|
|
||||||
})
|
|
||||||
organizations.value = orgResult.data ?? []
|
|
||||||
|
|
||||||
if (!selectedOrganization.value && organizations.value.length > 0) {
|
|
||||||
selectedOrganization.value = organizations.value[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch active member
|
|
||||||
const activeMemberResult = await client.organization.getActiveMember({
|
|
||||||
fetchOptions: {
|
|
||||||
headers
|
|
||||||
}
|
|
||||||
})
|
|
||||||
activeMember.value = activeMemberResult.data || null
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => selectedOrganization.value,
|
|
||||||
async (newValue) => {
|
|
||||||
if (newValue) {
|
|
||||||
await client.organization.setActive({
|
|
||||||
organizationId: newValue?.id
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (import.meta.client) {
|
|
||||||
client.$store.listen('$sessionSignal', async (signal) => {
|
|
||||||
if (!signal) return
|
|
||||||
await fetchSession()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function isPublicPath(path?: string) {
|
|
||||||
const finalPath = path ?? route.path
|
|
||||||
const publicRoutes = ['/login', '/signup', '/accept-invitation']
|
|
||||||
return publicRoutes.some((path) => finalPath.startsWith(path))
|
|
||||||
}
|
|
||||||
|
|
||||||
async function signOut({ redirectTo }: { redirectTo?: RouteLocationRaw } = {}) {
|
|
||||||
const res = await client.signOut()
|
|
||||||
if (res.error) {
|
|
||||||
console.error('Error signing out:', res.error)
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
session.value = null
|
|
||||||
user.value = null
|
|
||||||
if (redirectTo) {
|
|
||||||
await navigateTo(redirectTo, { external: true })
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
session,
|
...authState,
|
||||||
user,
|
...authActions,
|
||||||
loggedIn: computed(() => !!session.value),
|
|
||||||
signIn: client.signIn,
|
|
||||||
deleteUser: client.deleteUser,
|
|
||||||
signOut,
|
|
||||||
organization: client.organization,
|
|
||||||
organizations,
|
|
||||||
selectedOrganization,
|
|
||||||
options,
|
|
||||||
fetchSession,
|
|
||||||
client,
|
client,
|
||||||
jwt,
|
deleteUser: client.deleteUser,
|
||||||
isPublicPath,
|
organization: client.organization
|
||||||
activeMember
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { UserApi, Configuration, type CreateUserDto, type UserDto } from '~/.api-client'
|
import { UserApi, Configuration, type CreateUserDto, type UserDto } from '~/.api-client'
|
||||||
import { cleanDoubleSlashes, withoutTrailingSlash } from 'ufo'
|
import { cleanDoubleSlashes, withoutTrailingSlash } from 'ufo'
|
||||||
|
import { useAuthState } from '~/composables/auth/useAuthState'
|
||||||
|
|
||||||
export function useUserApi() {
|
export function useUserApi() {
|
||||||
const appBaseUrl = useRuntimeConfig().app.baseURL
|
const appBaseUrl = useRuntimeConfig().app.baseURL
|
||||||
const { serverApiBaseUrl, serverApiBasePath, clientProxyBasePath } = useRuntimeConfig().public
|
const { serverApiBaseUrl, serverApiBasePath, clientProxyBasePath } = useRuntimeConfig().public
|
||||||
const { jwt } = useAuth()
|
const { jwt } = useAuthState()
|
||||||
|
|
||||||
const basePath = withoutTrailingSlash(
|
const basePath = withoutTrailingSlash(
|
||||||
cleanDoubleSlashes(import.meta.client ? appBaseUrl + clientProxyBasePath : serverApiBaseUrl + serverApiBasePath)
|
cleanDoubleSlashes(import.meta.client ? appBaseUrl + clientProxyBasePath : serverApiBaseUrl + serverApiBasePath)
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ definePageMeta({ layout: 'auth' })
|
|||||||
useSeoMeta({ title: 'Login' })
|
useSeoMeta({ title: 'Login' })
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const { signIn } = useUserStore()
|
const { signIn } = useAuth()
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ definePageMeta({ layout: 'auth' })
|
|||||||
useSeoMeta({ title: 'Sign up' })
|
useSeoMeta({ title: 'Sign up' })
|
||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const { signUp } = useUserStore()
|
const { signUp } = useAuth()
|
||||||
|
|
||||||
const fields = [
|
const fields = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { RouteLocationRaw } from '#vue-router'
|
import type { RouteLocationRaw } from '#vue-router'
|
||||||
import type { useAuth } from '~/composables/useAuth'
|
import type { useAuthClient } from '~/composables/auth/useAuthClient'
|
||||||
|
|
||||||
export interface RuntimeAuthConfig {
|
export interface RuntimeAuthConfig {
|
||||||
redirectUserTo: RouteLocationRaw | string
|
redirectUserTo: RouteLocationRaw | string
|
||||||
@@ -7,7 +7,7 @@ export interface RuntimeAuthConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
// 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']
|
type Client = ReturnType<typeof useAuthClient>['client']
|
||||||
export type Session = Client['$Infer']['Session']
|
export type Session = Client['$Infer']['Session']
|
||||||
export type User = Session['user']
|
export type User = Session['user']
|
||||||
export type ActiveOrganization = Client['$Infer']['ActiveOrganization']
|
export type ActiveOrganization = Client['$Infer']['ActiveOrganization']
|
||||||
@@ -15,7 +15,7 @@ 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 Member = Client['$Infer']['Member']
|
||||||
export type ListMembersOptions = Parameters<Client['organization']['listMembers']>[0]
|
export type ListMembersOptions = Parameters<Client['organization']['listMembers']>[0]
|
||||||
export type ListMembersResponse = Awaited<ReturnType<Client['organization']['listMembers']>>
|
export type ListMembersResponse = Awaited<ReturnType<Client['organization']['listMembers']>>['data']
|
||||||
export type ListMembersQuery = NonNullable<ListMembersOptions>['query']
|
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 =
|
||||||
@@ -24,4 +24,4 @@ export type CustomInvitation =
|
|||||||
organizationSlug: string
|
organizationSlug: string
|
||||||
inviterEmail: string
|
inviterEmail: string
|
||||||
})
|
})
|
||||||
| null
|
| null
|
||||||
Reference in New Issue
Block a user