Files
crewli/apps/portal/components.d.ts
bert.hausmans b6a3a17b0a feat(form-builder): detect duplicate submissions by email on same form schema
Informational hint on the confirmation page when the same email has
already submitted the form. Not a block — the submission proceeds
normally. Privacy-safe: only shown to the submitter themselves.

Scope: same form_schema_id only. Cross-form/cross-event detection
would leak info about other forms.

- New FormSubmissionDuplicateDetector service queries by
  form_submissions.public_submitter_email (trim + case-insensitive)
  scoped to the schema, status=submitted, excluding the current
  submission. Errors are swallowed + logged so a detector failure
  never blocks the submit response.
- PublicFormSubmissionController enriches the submit response by
  setting a transient duplicate_submission_data attribute on the
  submission before resource serialisation.
- PublicFormSubmissionResource serialises a duplicate_submission
  block with count, first_submitted_at, plus backend-authored
  Dutch title + body (plural-agreement + IntlDateFormatter for
  "23 april 2026"-style long-form dates). Null when no priors,
  no email, or detector error.
- DuplicateSubmissionHint.vue (warning-typed tonal VAlert) above
  IdentityMatchBanner on FormConfirmation. Prefers backend copy
  with Intl-based Dutch date fallback for safety.
- 16 new backend assertions across the detector and the full
  submit-response flow; 5 new Vitest assertions for the hint.

Note on scope: spec suggested extracting email from values via
schema binding; the codebase's public flow captures submitter
email in a guaranteed column (public_submitter_email) populated
by the stepper's Contactgegevens step. Using that directly is
both simpler and more correct for the duplicate-by-submitter
semantic. When FORM-05's binding-based extractor lands, this
detector can migrate without changing its public API.

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

85 lines
7.0 KiB
TypeScript

/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AppAutocomplete: typeof import('./src/@core/components/app-form-elements/AppAutocomplete.vue')['default']
AppBarSearch: typeof import('./src/@core/components/AppBarSearch.vue')['default']
AppCardActions: typeof import('./src/@core/components/cards/AppCardActions.vue')['default']
AppCardCode: typeof import('./src/@core/components/cards/AppCardCode.vue')['default']
AppCombobox: typeof import('./src/@core/components/app-form-elements/AppCombobox.vue')['default']
AppDateTimePicker: typeof import('./src/@core/components/app-form-elements/AppDateTimePicker.vue')['default']
AppDrawerHeaderSection: typeof import('./src/@core/components/AppDrawerHeaderSection.vue')['default']
AppLoadingIndicator: typeof import('./src/components/AppLoadingIndicator.vue')['default']
AppSelect: typeof import('./src/@core/components/app-form-elements/AppSelect.vue')['default']
AppStepper: typeof import('./src/@core/components/AppStepper.vue')['default']
AppTextarea: typeof import('./src/@core/components/app-form-elements/AppTextarea.vue')['default']
AppTextField: typeof import('./src/@core/components/app-form-elements/AppTextField.vue')['default']
BuyNow: typeof import('./src/@core/components/BuyNow.vue')['default']
CardStatisticsHorizontal: typeof import('./src/@core/components/cards/CardStatisticsHorizontal.vue')['default']
CardStatisticsVertical: typeof import('./src/@core/components/cards/CardStatisticsVertical.vue')['default']
CardStatisticsVerticalSimple: typeof import('./src/@core/components/CardStatisticsVerticalSimple.vue')['default']
ClaimenTab: typeof import('./src/components/event/ClaimenTab.vue')['default']
CustomCheckboxes: typeof import('./src/@core/components/app-form-elements/CustomCheckboxes.vue')['default']
CustomCheckboxesWithIcon: typeof import('./src/@core/components/app-form-elements/CustomCheckboxesWithIcon.vue')['default']
CustomCheckboxesWithImage: typeof import('./src/@core/components/app-form-elements/CustomCheckboxesWithImage.vue')['default']
CustomizerSection: typeof import('./src/@core/components/CustomizerSection.vue')['default']
CustomRadios: typeof import('./src/@core/components/app-form-elements/CustomRadios.vue')['default']
CustomRadiosWithIcon: typeof import('./src/@core/components/app-form-elements/CustomRadiosWithIcon.vue')['default']
CustomRadiosWithImage: typeof import('./src/@core/components/app-form-elements/CustomRadiosWithImage.vue')['default']
DialogCloseBtn: typeof import('./src/@core/components/DialogCloseBtn.vue')['default']
DropZone: typeof import('./src/@core/components/DropZone.vue')['default']
DuplicateSubmissionHint: typeof import('./src/components/public-form/DuplicateSubmissionHint.vue')['default']
EventCard: typeof import('./src/components/portal/EventCard.vue')['default']
FieldAvailabilityPicker: typeof import('./src/components/public-form/FieldAvailabilityPicker.vue')['default']
FieldBoolean: typeof import('./src/components/public-form/FieldBoolean.vue')['default']
FieldCheckboxList: typeof import('./src/components/public-form/FieldCheckboxList.vue')['default']
FieldDate: typeof import('./src/components/public-form/FieldDate.vue')['default']
FieldEmail: typeof import('./src/components/public-form/FieldEmail.vue')['default']
FieldHeading: typeof import('./src/components/public-form/FieldHeading.vue')['default']
FieldMultiselect: typeof import('./src/components/public-form/FieldMultiselect.vue')['default']
FieldNumber: typeof import('./src/components/public-form/FieldNumber.vue')['default']
FieldParagraph: typeof import('./src/components/public-form/FieldParagraph.vue')['default']
FieldPhone: typeof import('./src/components/public-form/FieldPhone.vue')['default']
FieldRadio: typeof import('./src/components/public-form/FieldRadio.vue')['default']
FieldRenderer: typeof import('./src/components/public-form/FieldRenderer.vue')['default']
FieldSectionPriority: typeof import('./src/components/public-form/FieldSectionPriority.vue')['default']
FieldSelect: typeof import('./src/components/public-form/FieldSelect.vue')['default']
FieldTagPicker: typeof import('./src/components/public-form/FieldTagPicker.vue')['default']
FieldText: typeof import('./src/components/public-form/FieldText.vue')['default']
FieldTextarea: typeof import('./src/components/public-form/FieldTextarea.vue')['default']
FieldUrl: typeof import('./src/components/public-form/FieldUrl.vue')['default']
FormConfirmation: typeof import('./src/components/public-form/FormConfirmation.vue')['default']
FormErrorState: typeof import('./src/components/public-form/FormErrorState.vue')['default']
FormStepper: typeof import('./src/components/public-form/FormStepper.vue')['default']
I18n: typeof import('./src/@core/components/I18n.vue')['default']
IdentityMatchBanner: typeof import('./src/components/public-form/IdentityMatchBanner.vue')['default']
InformatieTab: typeof import('./src/components/event/InformatieTab.vue')['default']
MfaChallengeCard: typeof import('./src/components/auth/MfaChallengeCard.vue')['default']
MfaDisableDialog: typeof import('./src/components/settings/MfaDisableDialog.vue')['default']
MfaEmailSetupDialog: typeof import('./src/components/settings/MfaEmailSetupDialog.vue')['default']
MfaTotpSetupDialog: typeof import('./src/components/settings/MfaTotpSetupDialog.vue')['default']
MoreBtn: typeof import('./src/@core/components/MoreBtn.vue')['default']
Notifications: typeof import('./src/@core/components/Notifications.vue')['default']
OverzichtTab: typeof import('./src/components/event/OverzichtTab.vue')['default']
PasswordRequirements: typeof import('./src/components/auth/PasswordRequirements.vue')['default']
ProductDescriptionEditor: typeof import('./src/@core/components/ProductDescriptionEditor.vue')['default']
RoosterTab: typeof import('./src/components/event/RoosterTab.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
ScrollToTop: typeof import('./src/@core/components/ScrollToTop.vue')['default']
Shortcuts: typeof import('./src/@core/components/Shortcuts.vue')['default']
StatusCard: typeof import('./src/components/portal/StatusCard.vue')['default']
SubmitterDetails: typeof import('./src/components/public-form/SubmitterDetails.vue')['default']
TablePagination: typeof import('./src/@core/components/TablePagination.vue')['default']
TheCustomizer: typeof import('./src/@core/components/TheCustomizer.vue')['default']
ThemeSwitcher: typeof import('./src/@core/components/ThemeSwitcher.vue')['default']
TiptapEditor: typeof import('./src/@core/components/TiptapEditor.vue')['default']
UserAvatarMenu: typeof import('./src/components/portal/UserAvatarMenu.vue')['default']
}
}