Files
crewli-old/apps/portal/src/composables/api/usePublicForm.ts
bert.hausmans dda60ed5e4 refactor(form-schema): extract schema types and schema-driven behaviors to shared package
Moves formBuilder types, formValidation, useConditionalLogic, useFormSteps,
and formatFieldValue from apps/portal/src to packages/form-schema/src.
Adds @form-schema path alias to both apps/portal and apps/app.
Vue field components remain per-app to allow independent visual evolution.
Behavior-neutral: all 35 Vitest tests green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 23:57:39 +02:00

111 lines
3.5 KiB
TypeScript

import { useMutation, useQuery } from '@tanstack/vue-query'
import type { AxiosError } from 'axios'
import type { MaybeRefOrGetter } from 'vue'
import { apiClient } from '@/lib/axios'
import type {
PublicFormErrorBody,
PublicFormSchema,
PublicFormSubmission,
SaveDraftBody,
StartDraftBody,
SubmitBody,
} from '@form-schema/types/formBuilder'
interface ApiResponse<T> {
data: T
message?: string
success?: boolean
}
/**
* The backend standardises public-form errors as
* { message, code?, errors? }
* See api/app/Exceptions/FormBuilder/PublicFormApiException.php.
*/
export type PublicFormAxiosError = AxiosError<PublicFormErrorBody>
const TERMINAL_STATUSES = new Set([404, 410, 409])
export function useFetchPublicFormSchema(token: MaybeRefOrGetter<string | null | undefined>) {
return useQuery({
queryKey: ['public-form-schema', () => toValue(token)],
queryFn: async (): Promise<PublicFormSchema> => {
const t = toValue(token)
if (!t) throw new Error('Missing public_token')
const { data } = await apiClient.get<ApiResponse<PublicFormSchema>>(`/public/forms/${t}`)
return data.data
},
enabled: () => !!toValue(token),
retry: (failureCount, error) => {
const status = (error as PublicFormAxiosError | undefined)?.response?.status
if (status && TERMINAL_STATUSES.has(status)) return false
return failureCount < 1
},
staleTime: 1000 * 60 * 5,
})
}
export function useCreateFormDraft(token: MaybeRefOrGetter<string | null | undefined>) {
return useMutation({
mutationFn: async (body: StartDraftBody): Promise<PublicFormSubmission> => {
const t = toValue(token)
if (!t) throw new Error('Missing public_token')
const { data } = await apiClient.post<ApiResponse<PublicFormSubmission>>(
`/public/forms/${t}/submissions`,
body,
)
return data.data
},
})
}
export function useSaveFormDraft(token: MaybeRefOrGetter<string | null | undefined>) {
return useMutation({
mutationFn: async ({ submissionId, body }: { submissionId: string; body: SaveDraftBody }): Promise<PublicFormSubmission> => {
const t = toValue(token)
if (!t) throw new Error('Missing public_token')
const { data } = await apiClient.put<ApiResponse<PublicFormSubmission>>(
`/public/forms/${t}/submissions/${submissionId}`,
body,
)
return data.data
},
})
}
export function useSubmitForm(token: MaybeRefOrGetter<string | null | undefined>) {
return useMutation({
mutationFn: async ({ submissionId, body }: { submissionId: string; body: SubmitBody }): Promise<PublicFormSubmission> => {
const t = toValue(token)
if (!t) throw new Error('Missing public_token')
const { data } = await apiClient.post<ApiResponse<PublicFormSubmission>>(
`/public/forms/${t}/submissions/${submissionId}/submit`,
body,
)
return data.data
},
})
}
export function extractErrorBody(err: unknown): PublicFormErrorBody | null {
const axiosErr = err as PublicFormAxiosError | undefined
const body = axiosErr?.response?.data
if (body && typeof body === 'object' && 'message' in body) return body as PublicFormErrorBody
return null
}
export function extractRetryAfterSeconds(err: unknown): number | null {
const axiosErr = err as PublicFormAxiosError | undefined
const raw = axiosErr?.response?.headers?.['retry-after']
if (!raw) return null
const n = Number(raw)
return Number.isFinite(n) && n >= 0 ? n : null
}