feat(fullstack): Add organization scoping for application form
This commit is contained in:
@@ -27,9 +27,9 @@ export function useApplicationForm() {
|
||||
}
|
||||
}
|
||||
|
||||
async function getAllApplicationForms(): Promise<PagedApplicationFormDto> {
|
||||
async function getAllApplicationForms(organizationId: string): Promise<PagedApplicationFormDto> {
|
||||
try {
|
||||
return await applicationFormApi.getAllApplicationForms()
|
||||
return await applicationFormApi.getAllApplicationForms(organizationId)
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof ResponseError) {
|
||||
console.error('Failed retrieving application forms:', e.response)
|
||||
|
||||
@@ -21,8 +21,8 @@ export function useApplicationFormApi() {
|
||||
return applicationFormApiClient.createApplicationForm({ createApplicationFormDto })
|
||||
}
|
||||
|
||||
async function getAllApplicationForms(): Promise<PagedApplicationFormDto> {
|
||||
return applicationFormApiClient.getAllApplicationForms()
|
||||
async function getAllApplicationForms(organizationId: string): Promise<PagedApplicationFormDto> {
|
||||
return applicationFormApiClient.getAllApplicationForms({ organizationId })
|
||||
}
|
||||
|
||||
async function getApplicationFormById(id: string): Promise<ApplicationFormDto> {
|
||||
|
||||
@@ -11,6 +11,22 @@ interface RuntimeAuthConfig {
|
||||
redirectGuestTo: RouteLocationRaw | string
|
||||
}
|
||||
|
||||
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?: any; logo?: string | null }[]
|
||||
>([])
|
||||
const selectedOrganization = ref<{
|
||||
id: string
|
||||
name: string
|
||||
createdAt: Date
|
||||
slug: string
|
||||
metadata?: any
|
||||
logo?: string | null
|
||||
} | null>(null)
|
||||
|
||||
export function useAuth() {
|
||||
const url = useRequestURL()
|
||||
const headers = import.meta.server ? useRequestHeaders() : undefined
|
||||
@@ -27,10 +43,6 @@ export function useAuth() {
|
||||
redirectUserTo: '/',
|
||||
redirectGuestTo: '/login'
|
||||
})
|
||||
const session = useState<InferSessionFromClient<ClientOptions> | null>('auth:session', () => null)
|
||||
const user = useState<InferUserFromClient<ClientOptions> | null>('auth:user', () => null)
|
||||
const sessionFetching = import.meta.server ? ref(false) : useState('auth:sessionFetching', () => false)
|
||||
const jwt = useState<string | null>('auth:jwt', () => null)
|
||||
|
||||
async function fetchSession() {
|
||||
if (sessionFetching.value) {
|
||||
@@ -43,13 +55,33 @@ export function useAuth() {
|
||||
headers
|
||||
}
|
||||
})
|
||||
jwt.value = (await client.token()).data?.token ?? null
|
||||
session.value = data?.session || null
|
||||
user.value = data?.user || null
|
||||
sessionFetching.value = false
|
||||
|
||||
// Fetch JWT - workaround for not working extraction of JWT out of session (https://github.com/better-auth/better-auth/issues/1835)
|
||||
jwt.value = (await client.token()).data?.token ?? null
|
||||
|
||||
// Fetch organization
|
||||
organizations.value = (await client.organization.list()).data ?? []
|
||||
if (!selectedOrganization.value && organizations.value.length > 0) {
|
||||
selectedOrganization.value = organizations.value[0]
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
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
|
||||
@@ -79,6 +111,8 @@ export function useAuth() {
|
||||
signUp: client.signUp,
|
||||
signOut,
|
||||
organization: client.organization,
|
||||
organizations,
|
||||
selectedOrganization,
|
||||
options,
|
||||
fetchSession,
|
||||
client,
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
<template #body>
|
||||
<div class="flex flex-col gap-4 sm:gap-6 lg:gap-12 w-full lg:max-w-4xl mx-auto">
|
||||
Erstelle Forumular für Organisation: {{ selectedOrganization?.name }}
|
||||
<UPageCard title="Ampelstatus" variant="naked" orientation="horizontal" class="mb-4">
|
||||
{{ ampelStatusEmoji }}
|
||||
</UPageCard>
|
||||
@@ -35,14 +36,14 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ComplianceStatus, type PagedApplicationFormDto } from '~/.api-client'
|
||||
import { ComplianceStatus, type PagedApplicationFormDto, type UserDto } from '~/.api-client'
|
||||
import { useApplicationFormValidator } from '~/composables/useApplicationFormValidator'
|
||||
import type { FormElementId } from '~/types/FormElement'
|
||||
|
||||
const { getAllApplicationFormTemplates } = useApplicationFormTemplate()
|
||||
const { createApplicationForm } = useApplicationForm()
|
||||
const { validateFormElements, getHighestComplianceStatus } = useApplicationFormValidator()
|
||||
const { user } = await useAuth()
|
||||
const { user, selectedOrganization } = useAuth()
|
||||
|
||||
const { data } = await useAsyncData<PagedApplicationFormDto>(async () => {
|
||||
return await getAllApplicationFormTemplates()
|
||||
@@ -92,8 +93,13 @@ const ampelStatusEmoji = computed(() => {
|
||||
|
||||
async function onSubmit() {
|
||||
if (applicationFormTemplate.value) {
|
||||
applicationFormTemplate.value.createdBy = user.value?.name ?? 'Unknown'
|
||||
applicationFormTemplate.value.lastModifiedBy = user.value?.name ?? 'Unknown'
|
||||
const userDto: UserDto = {
|
||||
id: user.value?.id ?? '',
|
||||
name: user.value?.name ?? 'Unknown'
|
||||
}
|
||||
applicationFormTemplate.value.createdBy = userDto
|
||||
applicationFormTemplate.value.lastModifiedBy = userDto
|
||||
applicationFormTemplate.value.organizationId = selectedOrganization.value?.id ?? ''
|
||||
|
||||
await createApplicationForm(applicationFormTemplate.value)
|
||||
await navigateTo('/')
|
||||
|
||||
@@ -7,6 +7,18 @@
|
||||
</template>
|
||||
|
||||
<template #right>
|
||||
Aktuelle Organisation
|
||||
<USelect
|
||||
v-model="selectedOrganizationId"
|
||||
:items="organizations"
|
||||
value-key="id"
|
||||
label-key="name"
|
||||
size="lg"
|
||||
:ui="{
|
||||
trailingIcon: 'group-data-[state=open]:rotate-180 transition-transform duration-200'
|
||||
}"
|
||||
class="w-48"
|
||||
/>
|
||||
<UDropdownMenu :items="items">
|
||||
<UButton icon="i-lucide-plus" size="md" class="rounded-full" />
|
||||
</UDropdownMenu>
|
||||
@@ -58,10 +70,17 @@ import type { ApplicationFormDto, PagedApplicationFormDto } from '~/.api-client'
|
||||
|
||||
const { getAllApplicationForms, deleteApplicationFormById } = useApplicationForm()
|
||||
const route = useRoute()
|
||||
const { organizations, selectedOrganization } = useAuth()
|
||||
|
||||
const { data } = await useAsyncData<PagedApplicationFormDto>(async () => {
|
||||
return await getAllApplicationForms()
|
||||
})
|
||||
const { data } = await useAsyncData<PagedApplicationFormDto>(
|
||||
async () => {
|
||||
if (!selectedOrganization.value) {
|
||||
throw new Error('No organization selected')
|
||||
}
|
||||
return await getAllApplicationForms(selectedOrganization.value.id)
|
||||
},
|
||||
{ watch: [selectedOrganization] }
|
||||
)
|
||||
|
||||
const isDeleteModalOpen = computed<boolean>({
|
||||
get: () => 'delete' in route.query,
|
||||
@@ -75,6 +94,16 @@ const applicationFormNameToDelete = computed(() => {
|
||||
return data?.value?.content.find((appForm) => appForm.id === route.query.id)
|
||||
})
|
||||
|
||||
const selectedOrganizationId = computed({
|
||||
get() {
|
||||
return selectedOrganization.value?.id
|
||||
},
|
||||
set(item) {
|
||||
// TODO: USelect triggers multiple times after single selection
|
||||
selectedOrganization.value = organizations.value.find((i) => i.id === item) ?? null
|
||||
}
|
||||
})
|
||||
|
||||
const items = [
|
||||
[
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user