Skip to content

Pattern d’intégration dans NFZ Studio

Dans NFZ Studio, FormBuilder doit rester un composant UI. La persistance doit être portée par un store métier ou un service Feathers/NFZ.

Architecture recommandée

txt
app/pages/console/forms/[id].vue

app/stores/dynamicFormsStore.ts

app/composables/useDynamicFormsRepository.ts

service NFZ dynamic-forms

MongoDB

Modèle métier conseillé

ts
// app/types/dynamic-form.ts
import type { FormKitSchemaDefinition } from '@formkit/core'

export type DynamicFormStatus = 'draft' | 'published' | 'archived'

export type DynamicFormDocument = {
  _id?: string
  name: string
  slug: string
  description?: string
  schemaVersion: 1
  schema: FormKitSchemaDefinition[]
  settings: Record<string, unknown>
  status: DynamicFormStatus
  createdAt?: string
  updatedAt?: string
}

Repository côté client

Dans un projet NFZ, remplace les $fetch par ton accès Feathers centralisé, par exemple useAdminFeathers() ou un store métier.

ts
// app/composables/useDynamicFormsRepository.ts
import type { FormKitSchemaDefinition } from '@formkit/core'

export type DynamicFormDocument = {
  _id?: string
  name: string
  slug: string
  schemaVersion: 1
  schema: FormKitSchemaDefinition[]
  settings: Record<string, unknown>
  status: 'draft' | 'published' | 'archived'
}

export function useDynamicFormsRepository() {
  async function get(id: string) {
    return await $fetch<DynamicFormDocument>(`/api/dynamic-forms/${id}`)
  }

  async function save(id: string, payload: DynamicFormDocument) {
    return await $fetch<DynamicFormDocument>(`/api/dynamic-forms/${id}`, {
      method: 'PATCH',
      body: payload,
    })
  }

  return {
    get,
    save,
  }
}

Page NFZ Studio type

vue
<!-- app/pages/console/forms/[id].vue -->
<script setup lang="ts">
import type { FormKitSchemaDefinition } from '@formkit/core'

type FormBuilderValues = Record<string, unknown>

type DynamicFormDocument = {
  _id?: string
  name: string
  slug: string
  schemaVersion: 1
  schema: FormKitSchemaDefinition[]
  values?: FormBuilderValues
  settings: Record<string, unknown>
  status: 'draft' | 'published' | 'archived'
}

const route = useRoute()
const repository = useDynamicFormsRepository()

const id = computed(() => String(route.params.id))
const builderId = computed(() => `nfz-studio-form-${id.value}`)
const pending = ref(false)
const saving = ref(false)

const form = ref<DynamicFormDocument>({
  name: 'Nouveau formulaire',
  slug: 'new-form',
  schemaVersion: 1,
  schema: [],
  values: {},
  settings: {},
  status: 'draft',
})

onMounted(async () => {
  pending.value = true

  try {
    form.value = await repository.get(id.value)
  }
  finally {
    pending.value = false
  }
})

async function handleSave(payload: {
  schema: FormKitSchemaDefinition[]
  values: FormBuilderValues
  settings: Record<string, unknown>
}) {
  saving.value = true

  try {
    form.value = await repository.save(id.value, {
      ...form.value,
      schema: payload.schema,
      values: payload.values,
      settings: payload.settings,
    })
  }
  finally {
    saving.value = false
  }
}
</script>

<template>
  <QPage padding>
    <QInnerLoading :showing="pending" />

    <FormBuilder
      v-if="!pending"
      :builder-id="builderId"
      v-model:schema="form.schema"
      v-model:values="form.values"
      :settings="form.settings"
      :title="form.name"
      :subtitle="`Statut : ${form.status}`"
      :disabled="saving"
      autosave
      height="calc(100vh - 48px)"
      @save="handleSave"
    >
      <template #toolbar-after>
        <QBadge :color="form.status === 'published' ? 'positive' : 'warning'" outline>
          {{ form.status }}
        </QBadge>
      </template>
    </FormBuilder>
  </QPage>
</template>

Service NFZ conseillé

Nom de service possible :

txt
dynamic-forms

Champs minimum :

txt
name
slug
schemaVersion
schema
settings
status
createdAt
updatedAt

Pour une application plus avancée, ajoute :

txt
ownerId
workspaceId
publishedAt
version
tags
permissions

Bonne pratique sécurité

Le builder est un outil d’administration. Dans NFZ Studio, protège la route avec :

txt
middleware route Nuxt
store session Pinia
RBAC côté service Feathers/NFZ
validation Zod côté backend

Le contrôle d’accès ne doit jamais reposer uniquement sur le masquage du composant dans l’interface.

QForm Builder — couche Nuxt 4 / Quasar / FormKit réutilisable.