@qite/tide-booking-component 1.4.93 → 1.4.94

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (591) hide show
  1. package/.prettierrc +9 -9
  2. package/.vs/ProjectSettings.json +3 -3
  3. package/.vs/VSWorkspaceState.json +5 -5
  4. package/README.md +24 -24
  5. package/build/build-cjs/index.js +48786 -29655
  6. package/build/build-cjs/src/booking-product/components/age-select.d.ts +3 -3
  7. package/build/build-cjs/src/booking-product/components/amount-input.d.ts +5 -5
  8. package/build/build-cjs/src/booking-product/components/date-range-picker/calendar-day.d.ts +8 -8
  9. package/build/build-cjs/src/booking-product/components/date-range-picker/calendar.d.ts +14 -14
  10. package/build/build-cjs/src/booking-product/components/date-range-picker/index.d.ts +16 -19
  11. package/build/build-cjs/src/booking-product/components/dates.d.ts +8 -8
  12. package/build/build-cjs/src/booking-product/components/footer.d.ts +5 -5
  13. package/build/build-cjs/src/booking-product/components/header.d.ts +6 -6
  14. package/build/build-cjs/src/booking-product/components/icon.d.ts +5 -5
  15. package/build/build-cjs/src/booking-product/components/list-view.d.ts +2 -2
  16. package/build/build-cjs/src/booking-product/components/product.d.ts +4 -4
  17. package/build/build-cjs/src/booking-product/components/rating.d.ts +1 -1
  18. package/build/build-cjs/src/booking-product/components/rooms.d.ts +4 -4
  19. package/build/build-cjs/src/booking-product/constants.d.ts +1 -1
  20. package/build/build-cjs/src/booking-product/index.d.ts +4 -4
  21. package/build/build-cjs/src/booking-product/settings-context.d.ts +1 -2
  22. package/build/build-cjs/src/booking-product/types.d.ts +21 -21
  23. package/build/build-cjs/src/booking-product/utils/api.d.ts +11 -2
  24. package/build/build-cjs/src/booking-product/utils/price.d.ts +10 -1
  25. package/build/build-cjs/src/booking-wizard/api-settings-slice.d.ts +3 -2
  26. package/build/build-cjs/src/booking-wizard/components/icon.d.ts +5 -5
  27. package/build/build-cjs/src/booking-wizard/components/labeled-input.d.ts +13 -13
  28. package/build/build-cjs/src/booking-wizard/components/labeled-select.d.ts +16 -16
  29. package/build/build-cjs/src/booking-wizard/components/message.d.ts +4 -4
  30. package/build/build-cjs/src/booking-wizard/components/multi-range-filter.d.ts +6 -6
  31. package/build/build-cjs/src/booking-wizard/components/phone-input.d.ts +11 -11
  32. package/build/build-cjs/src/booking-wizard/components/print-offer-button.d.ts +11 -11
  33. package/build/build-cjs/src/booking-wizard/components/product-card.d.ts +3 -3
  34. package/build/build-cjs/src/booking-wizard/components/step-indicator.d.ts +1 -1
  35. package/build/build-cjs/src/booking-wizard/components/step-route.d.ts +3 -3
  36. package/build/build-cjs/src/booking-wizard/features/booking/api.d.ts +26 -7
  37. package/build/build-cjs/src/booking-wizard/features/booking/booking-self-contained.d.ts +3 -3
  38. package/build/build-cjs/src/booking-wizard/features/booking/booking-slice.d.ts +102 -42
  39. package/build/build-cjs/src/booking-wizard/features/booking/booking.d.ts +3 -3
  40. package/build/build-cjs/src/booking-wizard/features/booking/constants.d.ts +8 -1
  41. package/build/build-cjs/src/booking-wizard/features/booking/selectors.d.ts +645 -479
  42. package/build/build-cjs/src/booking-wizard/features/confirmation/confirmation.d.ts +1 -2
  43. package/build/build-cjs/src/booking-wizard/features/error/error.d.ts +1 -2
  44. package/build/build-cjs/src/booking-wizard/features/flight-options/flight-filter.d.ts +3 -3
  45. package/build/build-cjs/src/booking-wizard/features/flight-options/flight-option-flight.d.ts +2 -2
  46. package/build/build-cjs/src/booking-wizard/features/flight-options/flight-option.d.ts +4 -4
  47. package/build/build-cjs/src/booking-wizard/features/flight-options/flight-utils.d.ts +9 -2
  48. package/build/build-cjs/src/booking-wizard/features/flight-options/index.d.ts +1 -2
  49. package/build/build-cjs/src/booking-wizard/features/price-details/price-details-api.d.ts +6 -1
  50. package/build/build-cjs/src/booking-wizard/features/price-details/price-details-slice.d.ts +15 -9
  51. package/build/build-cjs/src/booking-wizard/features/price-details/selectors.d.ts +303 -287
  52. package/build/build-cjs/src/booking-wizard/features/product-options/none-option.d.ts +3 -3
  53. package/build/build-cjs/src/booking-wizard/features/product-options/option-booking-airline-group.d.ts +2 -2
  54. package/build/build-cjs/src/booking-wizard/features/product-options/option-booking-group.d.ts +6 -6
  55. package/build/build-cjs/src/booking-wizard/features/product-options/option-item.d.ts +5 -5
  56. package/build/build-cjs/src/booking-wizard/features/product-options/option-pax-card.d.ts +4 -4
  57. package/build/build-cjs/src/booking-wizard/features/product-options/option-pax-group.d.ts +7 -7
  58. package/build/build-cjs/src/booking-wizard/features/product-options/option-room.d.ts +5 -5
  59. package/build/build-cjs/src/booking-wizard/features/product-options/option-unit-group.d.ts +7 -7
  60. package/build/build-cjs/src/booking-wizard/features/product-options/option-units-card.d.ts +3 -3
  61. package/build/build-cjs/src/booking-wizard/features/product-options/options-form.d.ts +1 -2
  62. package/build/build-cjs/src/booking-wizard/features/room-options/index.d.ts +1 -2
  63. package/build/build-cjs/src/booking-wizard/features/room-options/room-utils.d.ts +19 -6
  64. package/build/build-cjs/src/booking-wizard/features/room-options/room.d.ts +6 -6
  65. package/build/build-cjs/src/booking-wizard/features/room-options/traveler-rooms.d.ts +3 -3
  66. package/build/build-cjs/src/booking-wizard/features/sidebar/index.d.ts +2 -2
  67. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +2 -2
  68. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar-util.d.ts +4 -2
  69. package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar.d.ts +24 -23
  70. package/build/build-cjs/src/booking-wizard/features/summary/summary-booking-option-pax.d.ts +1 -1
  71. package/build/build-cjs/src/booking-wizard/features/summary/summary-booking-option-unit.d.ts +1 -1
  72. package/build/build-cjs/src/booking-wizard/features/summary/summary-flight.d.ts +2 -2
  73. package/build/build-cjs/src/booking-wizard/features/summary/summary-per-booking-option-group.d.ts +1 -1
  74. package/build/build-cjs/src/booking-wizard/features/summary/summary-per-pax-option-group.d.ts +1 -1
  75. package/build/build-cjs/src/booking-wizard/features/summary/summary-per-unit-option-group.d.ts +1 -1
  76. package/build/build-cjs/src/booking-wizard/features/summary/summary-slice.d.ts +3 -3
  77. package/build/build-cjs/src/booking-wizard/features/summary/summary.d.ts +1 -2
  78. package/build/build-cjs/src/booking-wizard/features/travelers-form/travelers-form-slice.d.ts +75 -75
  79. package/build/build-cjs/src/booking-wizard/features/travelers-form/travelers-form-util.d.ts +4 -4
  80. package/build/build-cjs/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
  81. package/build/build-cjs/src/booking-wizard/features/travelers-form/type-ahead-input.d.ts +11 -11
  82. package/build/build-cjs/src/booking-wizard/features/travelers-form/validate-form.d.ts +8 -1
  83. package/build/build-cjs/src/booking-wizard/index.d.ts +6 -6
  84. package/build/build-cjs/src/booking-wizard/settings-context.d.ts +1 -2
  85. package/build/build-cjs/src/booking-wizard/store.d.ts +40 -22
  86. package/build/build-cjs/src/booking-wizard/types.d.ts +240 -239
  87. package/build/build-cjs/src/booking-wizard/use-offer-printer.d.ts +8 -8
  88. package/build/build-cjs/src/content/components/LanguageSwitcher.d.ts +5 -5
  89. package/build/build-cjs/src/content/components/accordion.d.ts +4 -4
  90. package/build/build-cjs/src/content/components/breadcrumb.d.ts +7 -7
  91. package/build/build-cjs/src/content/components/faq.d.ts +4 -4
  92. package/build/build-cjs/src/content/components/gallery.d.ts +6 -6
  93. package/build/build-cjs/src/content/components/icon.d.ts +5 -5
  94. package/build/build-cjs/src/content/components/image-with-text.d.ts +18 -18
  95. package/build/build-cjs/src/content/components/slider.d.ts +5 -5
  96. package/build/build-cjs/src/content/featured-trips/types.d.ts +8 -8
  97. package/build/build-cjs/src/content/features/content-page/content-page-self-contained.d.ts +1 -1
  98. package/build/build-cjs/src/content/footer/types.d.ts +17 -17
  99. package/build/build-cjs/src/content/header/types.d.ts +23 -20
  100. package/build/build-cjs/src/content/image-card-grid/types.d.ts +8 -8
  101. package/build/build-cjs/src/content/image-with-text-section/types.d.ts +15 -15
  102. package/build/build-cjs/src/content/login/login-services.d.ts +6 -1
  103. package/build/build-cjs/src/content/login/types.d.ts +19 -19
  104. package/build/build-cjs/src/content/navbar/placeholderData.d.ts +2 -2
  105. package/build/build-cjs/src/content/navbar/types.d.ts +22 -22
  106. package/build/build-cjs/src/index.d.ts +17 -1
  107. package/build/build-cjs/src/qsm/components/date-range-picker/calendar-day.d.ts +7 -7
  108. package/build/build-cjs/src/qsm/components/date-range-picker/calendar.d.ts +18 -18
  109. package/build/build-cjs/src/qsm/components/date-range-picker/index.d.ts +5 -5
  110. package/build/build-cjs/src/qsm/components/double-search-input-group/index.d.ts +2 -2
  111. package/build/build-cjs/src/qsm/components/icon.d.ts +5 -5
  112. package/build/build-cjs/src/qsm/components/item-picker/index.d.ts +7 -7
  113. package/build/build-cjs/src/qsm/components/search-input/index.d.ts +9 -9
  114. package/build/build-cjs/src/qsm/components/search-input-group/index.d.ts +7 -7
  115. package/build/build-cjs/src/qsm/index.d.ts +1 -1
  116. package/build/build-cjs/src/qsm/store/qsm-slice.d.ts +110 -58
  117. package/build/build-cjs/src/qsm/store/qsm-store.d.ts +20 -7
  118. package/build/build-cjs/src/qsm/types.d.ts +59 -59
  119. package/build/build-cjs/src/search-results/components/filters/filters.d.ts +5 -5
  120. package/build/build-cjs/src/search-results/components/filters/flight-filters.d.ts +3 -3
  121. package/build/build-cjs/src/search-results/components/flight/flight-banner.d.ts +2 -2
  122. package/build/build-cjs/src/search-results/components/flight/flight-card.d.ts +1 -1
  123. package/build/build-cjs/src/search-results/components/flight/flight-leg.d.ts +1 -1
  124. package/build/build-cjs/src/search-results/components/flight/flight-path.d.ts +1 -1
  125. package/build/build-cjs/src/search-results/components/flight/flight-results.d.ts +2 -2
  126. package/build/build-cjs/src/search-results/components/flight/flight-search-context/index.d.ts +29 -29
  127. package/build/build-cjs/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +5 -5
  128. package/build/build-cjs/src/search-results/components/flight/flight-selection/independent-flight-selection.d.ts +1 -1
  129. package/build/build-cjs/src/search-results/components/flight/flight-selection/index.d.ts +2 -2
  130. package/build/build-cjs/src/search-results/components/flight/flight-selection/paired-flight-option.d.ts +1 -1
  131. package/build/build-cjs/src/search-results/components/flight/flight-selection/paired-flight-selection.d.ts +1 -1
  132. package/build/build-cjs/src/search-results/components/group-tour/group-tour-card.d.ts +3 -3
  133. package/build/build-cjs/src/search-results/components/group-tour/group-tour-results.d.ts +1 -1
  134. package/build/build-cjs/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -1
  135. package/build/build-cjs/src/search-results/components/hotel/hotel-card.d.ts +2 -2
  136. package/build/build-cjs/src/search-results/components/icon.d.ts +6 -6
  137. package/build/build-cjs/src/search-results/components/item-picker/index.d.ts +8 -8
  138. package/build/build-cjs/src/search-results/components/itinerary/index.d.ts +3 -3
  139. package/build/build-cjs/src/search-results/components/multi-range-filter.d.ts +6 -6
  140. package/build/build-cjs/src/search-results/components/round-trip/round-trip-results.d.ts +1 -2
  141. package/build/build-cjs/src/search-results/components/search-results-container/flight-search-results.d.ts +1 -1
  142. package/build/build-cjs/src/search-results/components/tab-views/index.d.ts +1 -2
  143. package/build/build-cjs/src/search-results/features/flights/flight-search-results-self-contained.d.ts +1 -2
  144. package/build/build-cjs/src/search-results/features/hotels/hotel-flight-search-results-self-contained.d.ts +1 -2
  145. package/build/build-cjs/src/search-results/features/hotels/hotel-search-results-self-contained.d.ts +1 -2
  146. package/build/build-cjs/src/search-results/features/roundtrips/roundtrip-search-results-self-contained.d.ts +1 -2
  147. package/build/build-cjs/src/search-results/index.d.ts +1 -1
  148. package/build/build-cjs/src/search-results/store/search-results-slice.d.ts +63 -26
  149. package/build/build-cjs/src/search-results/store/search-results-store.d.ts +20 -7
  150. package/build/build-cjs/src/search-results/types.d.ts +104 -104
  151. package/build/build-cjs/src/search-results/utils/flight-utils.d.ts +6 -1
  152. package/build/build-cjs/src/search-results/utils/search-results-utils.d.ts +10 -2
  153. package/build/build-cjs/src/shared/components/flyin/accommodation-flyin.d.ts +3 -3
  154. package/build/build-cjs/src/shared/components/flyin/flights-flyin.d.ts +2 -2
  155. package/build/build-cjs/src/shared/components/flyin/flyin.d.ts +7 -7
  156. package/build/build-cjs/src/shared/components/flyin/group-tour-flyin.d.ts +3 -3
  157. package/build/build-cjs/src/shared/components/icon.d.ts +5 -5
  158. package/build/build-cjs/src/shared/components/loader.d.ts +1 -1
  159. package/build/build-cjs/src/shared/types.d.ts +9 -9
  160. package/build/build-cjs/src/shared/utils/localization-util.d.ts +395 -395
  161. package/build/build-esm/index.js +48504 -29596
  162. package/build/build-esm/src/booking-product/components/age-select.d.ts +3 -3
  163. package/build/build-esm/src/booking-product/components/amount-input.d.ts +5 -5
  164. package/build/build-esm/src/booking-product/components/date-range-picker/calendar-day.d.ts +8 -8
  165. package/build/build-esm/src/booking-product/components/date-range-picker/calendar.d.ts +14 -14
  166. package/build/build-esm/src/booking-product/components/date-range-picker/index.d.ts +16 -19
  167. package/build/build-esm/src/booking-product/components/dates.d.ts +8 -8
  168. package/build/build-esm/src/booking-product/components/footer.d.ts +5 -5
  169. package/build/build-esm/src/booking-product/components/header.d.ts +6 -6
  170. package/build/build-esm/src/booking-product/components/icon.d.ts +5 -5
  171. package/build/build-esm/src/booking-product/components/list-view.d.ts +2 -2
  172. package/build/build-esm/src/booking-product/components/product.d.ts +4 -4
  173. package/build/build-esm/src/booking-product/components/rating.d.ts +1 -1
  174. package/build/build-esm/src/booking-product/components/rooms.d.ts +4 -4
  175. package/build/build-esm/src/booking-product/constants.d.ts +1 -1
  176. package/build/build-esm/src/booking-product/index.d.ts +4 -4
  177. package/build/build-esm/src/booking-product/settings-context.d.ts +1 -2
  178. package/build/build-esm/src/booking-product/types.d.ts +21 -21
  179. package/build/build-esm/src/booking-product/utils/api.d.ts +11 -2
  180. package/build/build-esm/src/booking-product/utils/price.d.ts +10 -1
  181. package/build/build-esm/src/booking-wizard/api-settings-slice.d.ts +3 -2
  182. package/build/build-esm/src/booking-wizard/components/icon.d.ts +5 -5
  183. package/build/build-esm/src/booking-wizard/components/labeled-input.d.ts +13 -13
  184. package/build/build-esm/src/booking-wizard/components/labeled-select.d.ts +16 -16
  185. package/build/build-esm/src/booking-wizard/components/message.d.ts +4 -4
  186. package/build/build-esm/src/booking-wizard/components/multi-range-filter.d.ts +6 -6
  187. package/build/build-esm/src/booking-wizard/components/phone-input.d.ts +11 -11
  188. package/build/build-esm/src/booking-wizard/components/print-offer-button.d.ts +11 -11
  189. package/build/build-esm/src/booking-wizard/components/product-card.d.ts +3 -3
  190. package/build/build-esm/src/booking-wizard/components/step-indicator.d.ts +1 -1
  191. package/build/build-esm/src/booking-wizard/components/step-route.d.ts +3 -3
  192. package/build/build-esm/src/booking-wizard/features/booking/api.d.ts +26 -7
  193. package/build/build-esm/src/booking-wizard/features/booking/booking-self-contained.d.ts +3 -3
  194. package/build/build-esm/src/booking-wizard/features/booking/booking-slice.d.ts +102 -42
  195. package/build/build-esm/src/booking-wizard/features/booking/booking.d.ts +3 -3
  196. package/build/build-esm/src/booking-wizard/features/booking/constants.d.ts +8 -1
  197. package/build/build-esm/src/booking-wizard/features/booking/selectors.d.ts +645 -479
  198. package/build/build-esm/src/booking-wizard/features/confirmation/confirmation.d.ts +1 -2
  199. package/build/build-esm/src/booking-wizard/features/error/error.d.ts +1 -2
  200. package/build/build-esm/src/booking-wizard/features/flight-options/flight-filter.d.ts +3 -3
  201. package/build/build-esm/src/booking-wizard/features/flight-options/flight-option-flight.d.ts +2 -2
  202. package/build/build-esm/src/booking-wizard/features/flight-options/flight-option.d.ts +4 -4
  203. package/build/build-esm/src/booking-wizard/features/flight-options/flight-utils.d.ts +9 -2
  204. package/build/build-esm/src/booking-wizard/features/flight-options/index.d.ts +1 -2
  205. package/build/build-esm/src/booking-wizard/features/price-details/price-details-api.d.ts +6 -1
  206. package/build/build-esm/src/booking-wizard/features/price-details/price-details-slice.d.ts +15 -9
  207. package/build/build-esm/src/booking-wizard/features/price-details/selectors.d.ts +303 -287
  208. package/build/build-esm/src/booking-wizard/features/product-options/none-option.d.ts +3 -3
  209. package/build/build-esm/src/booking-wizard/features/product-options/option-booking-airline-group.d.ts +2 -2
  210. package/build/build-esm/src/booking-wizard/features/product-options/option-booking-group.d.ts +6 -6
  211. package/build/build-esm/src/booking-wizard/features/product-options/option-item.d.ts +5 -5
  212. package/build/build-esm/src/booking-wizard/features/product-options/option-pax-card.d.ts +4 -4
  213. package/build/build-esm/src/booking-wizard/features/product-options/option-pax-group.d.ts +7 -7
  214. package/build/build-esm/src/booking-wizard/features/product-options/option-room.d.ts +5 -5
  215. package/build/build-esm/src/booking-wizard/features/product-options/option-unit-group.d.ts +7 -7
  216. package/build/build-esm/src/booking-wizard/features/product-options/option-units-card.d.ts +3 -3
  217. package/build/build-esm/src/booking-wizard/features/product-options/options-form.d.ts +1 -2
  218. package/build/build-esm/src/booking-wizard/features/room-options/index.d.ts +1 -2
  219. package/build/build-esm/src/booking-wizard/features/room-options/room-utils.d.ts +19 -6
  220. package/build/build-esm/src/booking-wizard/features/room-options/room.d.ts +6 -6
  221. package/build/build-esm/src/booking-wizard/features/room-options/traveler-rooms.d.ts +3 -3
  222. package/build/build-esm/src/booking-wizard/features/sidebar/index.d.ts +2 -2
  223. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-flight.d.ts +2 -2
  224. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar-util.d.ts +4 -2
  225. package/build/build-esm/src/booking-wizard/features/sidebar/sidebar.d.ts +24 -23
  226. package/build/build-esm/src/booking-wizard/features/summary/summary-booking-option-pax.d.ts +1 -1
  227. package/build/build-esm/src/booking-wizard/features/summary/summary-booking-option-unit.d.ts +1 -1
  228. package/build/build-esm/src/booking-wizard/features/summary/summary-flight.d.ts +2 -2
  229. package/build/build-esm/src/booking-wizard/features/summary/summary-per-booking-option-group.d.ts +1 -1
  230. package/build/build-esm/src/booking-wizard/features/summary/summary-per-pax-option-group.d.ts +1 -1
  231. package/build/build-esm/src/booking-wizard/features/summary/summary-per-unit-option-group.d.ts +1 -1
  232. package/build/build-esm/src/booking-wizard/features/summary/summary-slice.d.ts +3 -3
  233. package/build/build-esm/src/booking-wizard/features/summary/summary.d.ts +1 -2
  234. package/build/build-esm/src/booking-wizard/features/travelers-form/travelers-form-slice.d.ts +75 -75
  235. package/build/build-esm/src/booking-wizard/features/travelers-form/travelers-form-util.d.ts +4 -4
  236. package/build/build-esm/src/booking-wizard/features/travelers-form/travelers-form.d.ts +1 -2
  237. package/build/build-esm/src/booking-wizard/features/travelers-form/type-ahead-input.d.ts +11 -11
  238. package/build/build-esm/src/booking-wizard/features/travelers-form/validate-form.d.ts +8 -1
  239. package/build/build-esm/src/booking-wizard/index.d.ts +6 -6
  240. package/build/build-esm/src/booking-wizard/settings-context.d.ts +1 -2
  241. package/build/build-esm/src/booking-wizard/store.d.ts +40 -22
  242. package/build/build-esm/src/booking-wizard/types.d.ts +240 -239
  243. package/build/build-esm/src/booking-wizard/use-offer-printer.d.ts +8 -8
  244. package/build/build-esm/src/content/components/LanguageSwitcher.d.ts +5 -5
  245. package/build/build-esm/src/content/components/accordion.d.ts +4 -4
  246. package/build/build-esm/src/content/components/breadcrumb.d.ts +7 -7
  247. package/build/build-esm/src/content/components/faq.d.ts +4 -4
  248. package/build/build-esm/src/content/components/gallery.d.ts +6 -6
  249. package/build/build-esm/src/content/components/icon.d.ts +5 -5
  250. package/build/build-esm/src/content/components/image-with-text.d.ts +18 -18
  251. package/build/build-esm/src/content/components/slider.d.ts +5 -5
  252. package/build/build-esm/src/content/featured-trips/types.d.ts +8 -8
  253. package/build/build-esm/src/content/features/content-page/content-page-self-contained.d.ts +1 -1
  254. package/build/build-esm/src/content/footer/types.d.ts +17 -17
  255. package/build/build-esm/src/content/header/types.d.ts +23 -20
  256. package/build/build-esm/src/content/image-card-grid/types.d.ts +8 -8
  257. package/build/build-esm/src/content/image-with-text-section/types.d.ts +15 -15
  258. package/build/build-esm/src/content/login/login-services.d.ts +6 -1
  259. package/build/build-esm/src/content/login/types.d.ts +19 -19
  260. package/build/build-esm/src/content/navbar/placeholderData.d.ts +2 -2
  261. package/build/build-esm/src/content/navbar/types.d.ts +22 -22
  262. package/build/build-esm/src/index.d.ts +17 -1
  263. package/build/build-esm/src/qsm/components/date-range-picker/calendar-day.d.ts +7 -7
  264. package/build/build-esm/src/qsm/components/date-range-picker/calendar.d.ts +18 -18
  265. package/build/build-esm/src/qsm/components/date-range-picker/index.d.ts +5 -5
  266. package/build/build-esm/src/qsm/components/double-search-input-group/index.d.ts +2 -2
  267. package/build/build-esm/src/qsm/components/icon.d.ts +5 -5
  268. package/build/build-esm/src/qsm/components/item-picker/index.d.ts +7 -7
  269. package/build/build-esm/src/qsm/components/search-input/index.d.ts +9 -9
  270. package/build/build-esm/src/qsm/components/search-input-group/index.d.ts +7 -7
  271. package/build/build-esm/src/qsm/index.d.ts +1 -1
  272. package/build/build-esm/src/qsm/store/qsm-slice.d.ts +110 -58
  273. package/build/build-esm/src/qsm/store/qsm-store.d.ts +20 -7
  274. package/build/build-esm/src/qsm/types.d.ts +59 -59
  275. package/build/build-esm/src/search-results/components/filters/filters.d.ts +5 -5
  276. package/build/build-esm/src/search-results/components/filters/flight-filters.d.ts +3 -3
  277. package/build/build-esm/src/search-results/components/flight/flight-banner.d.ts +2 -2
  278. package/build/build-esm/src/search-results/components/flight/flight-card.d.ts +1 -1
  279. package/build/build-esm/src/search-results/components/flight/flight-leg.d.ts +1 -1
  280. package/build/build-esm/src/search-results/components/flight/flight-path.d.ts +1 -1
  281. package/build/build-esm/src/search-results/components/flight/flight-results.d.ts +2 -2
  282. package/build/build-esm/src/search-results/components/flight/flight-search-context/index.d.ts +29 -29
  283. package/build/build-esm/src/search-results/components/flight/flight-selection/independent-flight-option.d.ts +5 -5
  284. package/build/build-esm/src/search-results/components/flight/flight-selection/independent-flight-selection.d.ts +1 -1
  285. package/build/build-esm/src/search-results/components/flight/flight-selection/index.d.ts +2 -2
  286. package/build/build-esm/src/search-results/components/flight/flight-selection/paired-flight-option.d.ts +1 -1
  287. package/build/build-esm/src/search-results/components/flight/flight-selection/paired-flight-selection.d.ts +1 -1
  288. package/build/build-esm/src/search-results/components/group-tour/group-tour-card.d.ts +3 -3
  289. package/build/build-esm/src/search-results/components/group-tour/group-tour-results.d.ts +1 -1
  290. package/build/build-esm/src/search-results/components/hotel/hotel-accommodation-results.d.ts +1 -1
  291. package/build/build-esm/src/search-results/components/hotel/hotel-card.d.ts +2 -2
  292. package/build/build-esm/src/search-results/components/icon.d.ts +6 -6
  293. package/build/build-esm/src/search-results/components/item-picker/index.d.ts +8 -8
  294. package/build/build-esm/src/search-results/components/itinerary/index.d.ts +3 -3
  295. package/build/build-esm/src/search-results/components/multi-range-filter.d.ts +6 -6
  296. package/build/build-esm/src/search-results/components/round-trip/round-trip-results.d.ts +1 -2
  297. package/build/build-esm/src/search-results/components/search-results-container/flight-search-results.d.ts +1 -1
  298. package/build/build-esm/src/search-results/components/tab-views/index.d.ts +1 -2
  299. package/build/build-esm/src/search-results/features/flights/flight-search-results-self-contained.d.ts +1 -2
  300. package/build/build-esm/src/search-results/features/hotels/hotel-flight-search-results-self-contained.d.ts +1 -2
  301. package/build/build-esm/src/search-results/features/hotels/hotel-search-results-self-contained.d.ts +1 -2
  302. package/build/build-esm/src/search-results/features/roundtrips/roundtrip-search-results-self-contained.d.ts +1 -2
  303. package/build/build-esm/src/search-results/index.d.ts +1 -1
  304. package/build/build-esm/src/search-results/store/search-results-slice.d.ts +63 -26
  305. package/build/build-esm/src/search-results/store/search-results-store.d.ts +20 -7
  306. package/build/build-esm/src/search-results/types.d.ts +104 -104
  307. package/build/build-esm/src/search-results/utils/flight-utils.d.ts +6 -1
  308. package/build/build-esm/src/search-results/utils/search-results-utils.d.ts +10 -2
  309. package/build/build-esm/src/shared/components/flyin/accommodation-flyin.d.ts +3 -3
  310. package/build/build-esm/src/shared/components/flyin/flights-flyin.d.ts +2 -2
  311. package/build/build-esm/src/shared/components/flyin/flyin.d.ts +7 -7
  312. package/build/build-esm/src/shared/components/flyin/group-tour-flyin.d.ts +3 -3
  313. package/build/build-esm/src/shared/components/icon.d.ts +5 -5
  314. package/build/build-esm/src/shared/components/loader.d.ts +1 -1
  315. package/build/build-esm/src/shared/types.d.ts +9 -9
  316. package/build/build-esm/src/shared/utils/localization-util.d.ts +395 -395
  317. package/package.json +2 -2
  318. package/rollup.config.js +16 -16
  319. package/src/booking-product/components/age-select.tsx +35 -35
  320. package/src/booking-product/components/amount-input.tsx +51 -51
  321. package/src/booking-product/components/date-range-picker/calendar-day.tsx +46 -46
  322. package/src/booking-product/components/date-range-picker/calendar.tsx +155 -155
  323. package/src/booking-product/components/date-range-picker/index.tsx +185 -185
  324. package/src/booking-product/components/dates.tsx +153 -153
  325. package/src/booking-product/components/footer.tsx +54 -54
  326. package/src/booking-product/components/header.tsx +57 -57
  327. package/src/booking-product/components/icon.tsx +200 -200
  328. package/src/booking-product/components/list-view.tsx +54 -54
  329. package/src/booking-product/components/product.tsx +379 -379
  330. package/src/booking-product/components/rating.tsx +21 -21
  331. package/src/booking-product/components/rooms.tsx +171 -171
  332. package/src/booking-product/constants.ts +1 -1
  333. package/src/booking-product/index.tsx +21 -21
  334. package/src/booking-product/settings-context.ts +16 -16
  335. package/src/booking-product/types.ts +30 -30
  336. package/src/booking-product/utils/api.ts +26 -26
  337. package/src/booking-product/utils/price.ts +28 -28
  338. package/src/booking-wizard/api-settings-slice.ts +24 -24
  339. package/src/booking-wizard/components/icon.tsx +398 -398
  340. package/src/booking-wizard/components/labeled-input.tsx +56 -56
  341. package/src/booking-wizard/components/labeled-select.tsx +54 -54
  342. package/src/booking-wizard/components/message.tsx +21 -21
  343. package/src/booking-wizard/components/multi-range-filter.tsx +99 -99
  344. package/src/booking-wizard/components/phone-input.tsx +146 -146
  345. package/src/booking-wizard/components/print-offer-button.tsx +53 -53
  346. package/src/booking-wizard/components/product-card.tsx +23 -23
  347. package/src/booking-wizard/components/step-indicator.tsx +57 -57
  348. package/src/booking-wizard/components/step-route.tsx +26 -26
  349. package/src/booking-wizard/declarations.d.ts +4 -4
  350. package/src/booking-wizard/features/booking/api.ts +44 -44
  351. package/src/booking-wizard/features/booking/booking-self-contained.tsx +318 -303
  352. package/src/booking-wizard/features/booking/booking-slice.ts +633 -625
  353. package/src/booking-wizard/features/booking/booking.tsx +342 -327
  354. package/src/booking-wizard/features/booking/constants.ts +16 -16
  355. package/src/booking-wizard/features/booking/selectors.ts +411 -406
  356. package/src/booking-wizard/features/confirmation/confirmation.tsx +90 -90
  357. package/src/booking-wizard/features/error/error.tsx +71 -71
  358. package/src/booking-wizard/features/flight-options/flight-filter.tsx +371 -371
  359. package/src/booking-wizard/features/flight-options/flight-option-flight.tsx +354 -354
  360. package/src/booking-wizard/features/flight-options/flight-option-modal.tsx +211 -211
  361. package/src/booking-wizard/features/flight-options/flight-option.tsx +57 -57
  362. package/src/booking-wizard/features/flight-options/flight-utils.ts +423 -423
  363. package/src/booking-wizard/features/flight-options/index.tsx +166 -166
  364. package/src/booking-wizard/features/price-details/price-details-api.ts +20 -20
  365. package/src/booking-wizard/features/price-details/price-details-slice.ts +79 -77
  366. package/src/booking-wizard/features/price-details/selectors.ts +118 -117
  367. package/src/booking-wizard/features/price-details/util.ts +115 -115
  368. package/src/booking-wizard/features/product-options/no-options.tsx +18 -18
  369. package/src/booking-wizard/features/product-options/none-option.tsx +73 -73
  370. package/src/booking-wizard/features/product-options/option-booking-airline-group.tsx +53 -53
  371. package/src/booking-wizard/features/product-options/option-booking-group.tsx +152 -152
  372. package/src/booking-wizard/features/product-options/option-item.tsx +236 -236
  373. package/src/booking-wizard/features/product-options/option-pax-card.tsx +159 -159
  374. package/src/booking-wizard/features/product-options/option-pax-group.tsx +122 -122
  375. package/src/booking-wizard/features/product-options/option-room.tsx +226 -226
  376. package/src/booking-wizard/features/product-options/option-unit-group.tsx +138 -138
  377. package/src/booking-wizard/features/product-options/option-units-card.tsx +148 -148
  378. package/src/booking-wizard/features/product-options/options-form.tsx +382 -382
  379. package/src/booking-wizard/features/room-options/index.tsx +132 -132
  380. package/src/booking-wizard/features/room-options/room-utils.ts +154 -154
  381. package/src/booking-wizard/features/room-options/room.tsx +123 -123
  382. package/src/booking-wizard/features/room-options/traveler-rooms.tsx +64 -64
  383. package/src/booking-wizard/features/sidebar/index.tsx +83 -81
  384. package/src/booking-wizard/features/sidebar/sidebar-flight.tsx +66 -66
  385. package/src/booking-wizard/features/sidebar/sidebar-util.ts +147 -147
  386. package/src/booking-wizard/features/sidebar/sidebar.tsx +330 -316
  387. package/src/booking-wizard/features/summary/summary-booking-option-pax.tsx +23 -23
  388. package/src/booking-wizard/features/summary/summary-booking-option-unit.tsx +23 -23
  389. package/src/booking-wizard/features/summary/summary-flight.tsx +36 -36
  390. package/src/booking-wizard/features/summary/summary-per-booking-option-group.tsx +60 -60
  391. package/src/booking-wizard/features/summary/summary-per-pax-option-group.tsx +56 -56
  392. package/src/booking-wizard/features/summary/summary-per-unit-option-group.tsx +58 -58
  393. package/src/booking-wizard/features/summary/summary-slice.ts +27 -27
  394. package/src/booking-wizard/features/summary/summary.tsx +562 -562
  395. package/src/booking-wizard/features/travelers-form/controls/gender-control.tsx +60 -60
  396. package/src/booking-wizard/features/travelers-form/travelers-form-slice.ts +157 -157
  397. package/src/booking-wizard/features/travelers-form/travelers-form-util.ts +10 -10
  398. package/src/booking-wizard/features/travelers-form/travelers-form.tsx +1101 -1101
  399. package/src/booking-wizard/features/travelers-form/type-ahead-input.tsx +85 -85
  400. package/src/booking-wizard/features/travelers-form/validate-form.ts +178 -178
  401. package/src/booking-wizard/index.tsx +27 -27
  402. package/src/booking-wizard/settings-context.ts +64 -64
  403. package/src/booking-wizard/store.ts +26 -26
  404. package/src/booking-wizard/types.ts +332 -331
  405. package/src/booking-wizard/use-offer-printer.ts +108 -108
  406. package/src/content/components/LanguageSwitcher.tsx +158 -158
  407. package/src/content/components/accordion.tsx +30 -30
  408. package/src/content/components/breadcrumb.tsx +67 -67
  409. package/src/content/components/contact.tsx +211 -211
  410. package/src/content/components/faq.tsx +42 -42
  411. package/src/content/components/gallery.tsx +153 -153
  412. package/src/content/components/icon.tsx +695 -695
  413. package/src/content/components/image-with-text.tsx +120 -120
  414. package/src/content/components/login.tsx +162 -162
  415. package/src/content/components/personal-contact-form.tsx +809 -809
  416. package/src/content/components/slider.tsx +237 -237
  417. package/src/content/featured-trips/featured-trip-card.tsx +48 -48
  418. package/src/content/featured-trips/index.tsx +19 -19
  419. package/src/content/featured-trips/types.ts +13 -13
  420. package/src/content/features/content-page/content-page-self-contained.tsx +895 -895
  421. package/src/content/footer/index.tsx +159 -159
  422. package/src/content/footer/types.ts +36 -36
  423. package/src/content/header/index.tsx +43 -43
  424. package/src/content/header/types.ts +26 -26
  425. package/src/content/image-card-grid/index.tsx +34 -34
  426. package/src/content/image-card-grid/types.ts +13 -13
  427. package/src/content/image-with-text-section/card.tsx +58 -58
  428. package/src/content/image-with-text-section/index.tsx +22 -22
  429. package/src/content/image-with-text-section/types.ts +20 -20
  430. package/src/content/login/confirm-component.tsx +149 -149
  431. package/src/content/login/index.tsx +70 -70
  432. package/src/content/login/login-component.tsx +159 -159
  433. package/src/content/login/login-services.ts +109 -109
  434. package/src/content/login/reset-password-component.tsx +191 -191
  435. package/src/content/login/types.ts +29 -29
  436. package/src/content/navbar/index.tsx +354 -354
  437. package/src/content/navbar/placeholderData.tsx +173 -173
  438. package/src/content/navbar/types.ts +43 -43
  439. package/src/index.ts +44 -44
  440. package/src/qsm/components/QSMContainer/qsm-container.tsx +512 -512
  441. package/src/qsm/components/date-picker/index.tsx +152 -152
  442. package/src/qsm/components/date-range-picker/calendar-day.tsx +49 -49
  443. package/src/qsm/components/date-range-picker/calendar.tsx +211 -211
  444. package/src/qsm/components/date-range-picker/index.tsx +404 -404
  445. package/src/qsm/components/double-search-input-group/index.tsx +78 -78
  446. package/src/qsm/components/icon.tsx +354 -354
  447. package/src/qsm/components/item-picker/index.tsx +69 -69
  448. package/src/qsm/components/mobile-filter-modal/index.tsx +307 -307
  449. package/src/qsm/components/search-input/index.tsx +91 -91
  450. package/src/qsm/components/search-input-group/index.tsx +199 -199
  451. package/src/qsm/components/travel-class-picker/index.tsx +28 -28
  452. package/src/qsm/components/travel-input/index.tsx +243 -243
  453. package/src/qsm/components/travel-input-group/index.tsx +114 -114
  454. package/src/qsm/components/travel-nationality-picker/index.tsx +28 -28
  455. package/src/qsm/components/travel-type-picker/index.tsx +28 -28
  456. package/src/qsm/index.tsx +26 -26
  457. package/src/qsm/qsm-configuration-context.ts +31 -31
  458. package/src/qsm/store/qsm-slice.ts +275 -275
  459. package/src/qsm/store/qsm-store.ts +13 -13
  460. package/src/qsm/types.ts +110 -110
  461. package/src/search-results/components/filters/filters.tsx +230 -230
  462. package/src/search-results/components/filters/flight-filters.tsx +671 -671
  463. package/src/search-results/components/flight/flight-banner.tsx +35 -35
  464. package/src/search-results/components/flight/flight-card.tsx +38 -38
  465. package/src/search-results/components/flight/flight-leg.tsx +61 -61
  466. package/src/search-results/components/flight/flight-path.tsx +23 -23
  467. package/src/search-results/components/flight/flight-results.tsx +208 -208
  468. package/src/search-results/components/flight/flight-search-context/index.tsx +628 -628
  469. package/src/search-results/components/flight/flight-selection/independent-flight-option.tsx +147 -147
  470. package/src/search-results/components/flight/flight-selection/independent-flight-selection.tsx +172 -172
  471. package/src/search-results/components/flight/flight-selection/index.tsx +19 -19
  472. package/src/search-results/components/flight/flight-selection/paired-flight-option.tsx +255 -255
  473. package/src/search-results/components/flight/flight-selection/paired-flight-selection.tsx +38 -38
  474. package/src/search-results/components/group-tour/group-tour-card.tsx +105 -105
  475. package/src/search-results/components/group-tour/group-tour-results.tsx +62 -62
  476. package/src/search-results/components/hotel/hotel-accommodation-results.tsx +176 -176
  477. package/src/search-results/components/hotel/hotel-card.tsx +113 -113
  478. package/src/search-results/components/icon.tsx +680 -680
  479. package/src/search-results/components/item-picker/index.tsx +81 -81
  480. package/src/search-results/components/itinerary/index.tsx +310 -310
  481. package/src/search-results/components/multi-range-filter.tsx +104 -104
  482. package/src/search-results/components/round-trip/round-trip-results.tsx +199 -199
  483. package/src/search-results/components/search-results-container/flight-search-results.tsx +137 -137
  484. package/src/search-results/components/search-results-container/search-results-container.tsx +893 -893
  485. package/src/search-results/components/spinner/spinner.tsx +16 -16
  486. package/src/search-results/components/tab-views/index.tsx +53 -53
  487. package/src/search-results/features/flights/flight-search-results-self-contained.tsx +294 -294
  488. package/src/search-results/features/hotels/hotel-flight-search-results-self-contained.tsx +143 -143
  489. package/src/search-results/features/hotels/hotel-search-results-self-contained.tsx +220 -220
  490. package/src/search-results/features/roundtrips/roundtrip-search-results-self-contained.tsx +65 -65
  491. package/src/search-results/index.tsx +24 -24
  492. package/src/search-results/search-results-configuration-context.ts +6 -6
  493. package/src/search-results/store/search-results-slice.ts +158 -158
  494. package/src/search-results/store/search-results-store.ts +13 -13
  495. package/src/search-results/types.ts +181 -181
  496. package/src/search-results/utils/flight-utils.ts +93 -93
  497. package/src/search-results/utils/search-results-utils.ts +251 -251
  498. package/src/shared/components/flyin/accommodation-flyin.tsx +422 -422
  499. package/src/shared/components/flyin/flights-flyin.tsx +503 -503
  500. package/src/shared/components/flyin/flyin.tsx +82 -82
  501. package/src/shared/components/flyin/group-tour-flyin.tsx +293 -293
  502. package/src/shared/components/icon.tsx +826 -826
  503. package/src/shared/components/loader.tsx +16 -16
  504. package/src/shared/translations/ar-SA.json +382 -381
  505. package/src/shared/translations/da-DK.json +382 -381
  506. package/src/shared/translations/de-DE.json +382 -381
  507. package/src/shared/translations/en-GB.json +386 -385
  508. package/src/shared/translations/es-ES.json +382 -381
  509. package/src/shared/translations/fr-BE.json +386 -385
  510. package/src/shared/translations/fr-FR.json +382 -381
  511. package/src/shared/translations/is-IS.json +382 -381
  512. package/src/shared/translations/it-IT.json +382 -381
  513. package/src/shared/translations/ja-JP.json +382 -381
  514. package/src/shared/translations/nl-BE.json +386 -385
  515. package/src/shared/translations/nl-NL.json +382 -381
  516. package/src/shared/translations/no-NO.json +382 -381
  517. package/src/shared/translations/pl-PL.json +382 -381
  518. package/src/shared/translations/pt-PT.json +382 -381
  519. package/src/shared/translations/sv-SE.json +382 -381
  520. package/src/shared/types.ts +31 -31
  521. package/src/shared/utils/class-util.ts +7 -7
  522. package/src/shared/utils/localization-util.ts +275 -275
  523. package/src/shared/utils/query-string-util.ts +91 -91
  524. package/src/shared/utils/tide-api-utils.ts +34 -34
  525. package/src/shared/utils/use-media-query-util.ts +19 -19
  526. package/styles/abstracts/_mixins.scss +74 -74
  527. package/styles/abstracts/_variables.scss +57 -57
  528. package/styles/base/_fonts.scss +2 -2
  529. package/styles/base/_normalize.scss +227 -227
  530. package/styles/base/_typography.scss +35 -35
  531. package/styles/booking-joker-variables.scss +596 -596
  532. package/styles/booking-product-variables.scss +330 -330
  533. package/styles/booking-product.scss +438 -438
  534. package/styles/booking-qsm-variables.scss +501 -501
  535. package/styles/booking-qsm.scss +52 -52
  536. package/styles/booking-search-results-variables.scss +728 -728
  537. package/styles/booking-search-results.scss +53 -53
  538. package/styles/booking-wizard-variables.scss +603 -603
  539. package/styles/booking-wizard.scss +61 -61
  540. package/styles/components/_accordion.scss +67 -67
  541. package/styles/components/_animations.scss +39 -39
  542. package/styles/components/_base.scss +107 -107
  543. package/styles/components/_booking.scss +872 -872
  544. package/styles/components/_breadcrumb.scss +92 -92
  545. package/styles/components/_button.scss +238 -238
  546. package/styles/components/_checkbox.scss +230 -230
  547. package/styles/components/_contact.scss +239 -239
  548. package/styles/components/_content.scss +336 -336
  549. package/styles/components/_cta.scss +238 -238
  550. package/styles/components/_date-list.scss +41 -41
  551. package/styles/components/_date-range-picker.scss +223 -223
  552. package/styles/components/_decrement-increment.scss +35 -35
  553. package/styles/components/_dropdown.scss +72 -72
  554. package/styles/components/_faq.scss +27 -27
  555. package/styles/components/_flight-option.scss +1419 -1419
  556. package/styles/components/_flyin.scss +727 -727
  557. package/styles/components/_footer.scss +141 -141
  558. package/styles/components/_form.scss +1634 -1634
  559. package/styles/components/_gallery.scss +314 -314
  560. package/styles/components/_header.scss +113 -113
  561. package/styles/components/_image-with-text.scss +206 -206
  562. package/styles/components/_img-slider.scss +175 -175
  563. package/styles/components/_info-message.scss +75 -75
  564. package/styles/components/_input.scss +35 -35
  565. package/styles/components/_list.scss +185 -185
  566. package/styles/components/_loader.scss +70 -70
  567. package/styles/components/_login.scss +140 -140
  568. package/styles/components/_mixins.scss +579 -579
  569. package/styles/components/_navbar.scss +765 -765
  570. package/styles/components/_passenger-picker.scss +306 -306
  571. package/styles/components/_phone-input.scss +8 -8
  572. package/styles/components/_placeholders.scss +165 -165
  573. package/styles/components/_pricing-summary.scss +163 -163
  574. package/styles/components/_qsm.scss +17 -17
  575. package/styles/components/_radiobutton.scss +170 -170
  576. package/styles/components/_search.scss +2009 -2009
  577. package/styles/components/_select-wrapper.scss +76 -76
  578. package/styles/components/_slider.scss +128 -128
  579. package/styles/components/_spinner.scss +29 -29
  580. package/styles/components/_step-indicators.scss +161 -161
  581. package/styles/components/_table.scss +81 -81
  582. package/styles/components/_tree.scss +648 -648
  583. package/styles/components/_typeahead.scss +275 -275
  584. package/styles/components/_variables.scss +89 -89
  585. package/styles/content-blocks-variables.scss +507 -507
  586. package/styles/content-blocks.scss +64 -64
  587. package/styles/font.scss +2 -2
  588. package/styles/qsm/_calendar.scss +274 -274
  589. package/styles/qsm/_qsm.scss +1094 -1094
  590. package/styles/search.scss +1200 -1200
  591. package/tsconfig.json +24 -24
@@ -1,809 +1,809 @@
1
- import React, { useMemo, useState } from 'react';
2
- import Icon from './icon';
3
-
4
- type ContactValues = {
5
- firstName: string;
6
- lastName: string;
7
- dateOfBirth: string;
8
- nationality: string;
9
- email: string;
10
- phone: string;
11
- message: string;
12
- street: string;
13
- houseNumber: string;
14
- box: string;
15
- postalCode: string;
16
- city: string;
17
- country: string;
18
- // traveler 1
19
- bookingType1: 'leisure' | 'business' | '';
20
- gender1: 'female' | 'male' | 'other' | '';
21
-
22
- // traveler 2
23
- bookingType2: 'leisure' | 'business' | '';
24
- gender2: 'female' | 'male' | 'other' | '';
25
- };
26
-
27
- type ContactErrors = Partial<Record<keyof ContactValues, string>>;
28
- // const [touched, setTouched] = useState<Partial<Record<keyof ContactValues, boolean>>>({});
29
-
30
- const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
31
- const phoneRegex = /^[+()\-.\s0-9]{8,20}$/;
32
-
33
- // Small helper: checks YYYY-MM-DD parses to a real date
34
- const isValidISODate = (value: string) => {
35
- if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
36
- const d = new Date(value);
37
- return !Number.isNaN(d.getTime()) && d.toISOString().slice(0, 10) === value;
38
- };
39
-
40
- // You can expand this list later (or load from API)
41
- const nationalityOptions = [
42
- { value: '', label: 'Selecteer nationaliteit' },
43
- { value: 'BE', label: 'Belgisch' },
44
- { value: 'NL', label: 'Nederlands' },
45
- { value: 'FR', label: 'Frans' },
46
- { value: 'DE', label: 'Duits' },
47
- { value: 'UK', label: 'Brits' },
48
- { value: 'US', label: 'Amerikaans' }
49
- ];
50
-
51
- const countryOptions = [
52
- { value: '', label: 'Selecteer land' },
53
- { value: 'BE', label: 'België' },
54
- { value: 'NL', label: 'Nederland' },
55
- { value: 'FR', label: 'Frankrijk' },
56
- { value: 'DE', label: 'Duitsland' },
57
- { value: 'LU', label: 'Luxemburg' },
58
- { value: 'UK', label: 'Verenigd Koninkrijk' }
59
- ];
60
-
61
- const PersonalContactForm: React.FC = () => {
62
- const [values, setValues] = useState<ContactValues>({
63
- firstName: '',
64
- lastName: '',
65
- dateOfBirth: '',
66
- nationality: '',
67
- email: '',
68
- phone: '',
69
- message: '',
70
- street: '',
71
- houseNumber: '',
72
- box: '',
73
- postalCode: '',
74
- city: '',
75
- country: '',
76
- bookingType1: '',
77
- gender1: '',
78
- bookingType2: '',
79
- gender2: ''
80
- });
81
-
82
- const [touched, setTouched] = useState<Partial<Record<keyof ContactValues, boolean>>>({});
83
- const [errors, setErrors] = useState<ContactErrors>({});
84
- const [submitted, setSubmitted] = useState(false);
85
- const [isNationalityOpen, setIsNationalityOpen] = useState(false);
86
- const [isCountryOpen, setIsCountryOpen] = useState(false);
87
-
88
- const postalCodeRegex = /^[0-9A-Za-z\s-]{3,10}$/;
89
-
90
- const validate = (v: ContactValues): ContactErrors => {
91
- const e: ContactErrors = {};
92
-
93
- if (!v.firstName.trim()) e.firstName = 'Voornaam is verplicht.';
94
- if (!v.lastName.trim()) e.lastName = 'Achternaam is verplicht.';
95
-
96
- if (!v.dateOfBirth.trim()) e.dateOfBirth = 'Geboortedatum is verplicht.';
97
- else if (!isValidISODate(v.dateOfBirth.trim())) e.dateOfBirth = 'Vul een geldige geboortedatum in.';
98
-
99
- if (!v.nationality.trim()) e.nationality = 'Nationaliteit is verplicht.';
100
-
101
- if (!v.email.trim()) e.email = 'Email is verplicht.';
102
- else if (!emailRegex.test(v.email.trim())) e.email = 'Vul een geldig e-mailadres in.';
103
-
104
- if (v.phone.trim() && !phoneRegex.test(v.phone.trim())) {
105
- e.phone = 'Vul een geldig telefoonnummer in.';
106
- }
107
-
108
- if (!v.message.trim()) e.message = 'Bericht is verplicht.';
109
- else if (v.message.trim().length < 10) e.message = 'Bericht moet minstens 10 tekens zijn.';
110
-
111
- if (!v.street.trim()) e.street = 'Straat is verplicht.';
112
- if (!v.houseNumber.trim()) e.houseNumber = 'Nummer is verplicht.';
113
-
114
- if (!v.postalCode.trim()) e.postalCode = 'Postcode is verplicht.';
115
- else if (!postalCodeRegex.test(v.postalCode.trim())) e.postalCode = 'Vul een geldige postcode in.';
116
-
117
- if (!v.city.trim()) e.city = 'Woonplaats is verplicht.';
118
- if (!v.country.trim()) e.country = 'Land is verplicht.';
119
-
120
- if (!v.gender1) e.gender1 = 'Geslacht is verplicht.';
121
- if (!v.gender2) e.gender2 = 'Geslacht is verplicht.';
122
-
123
- if (!v.bookingType1) e.bookingType1 = 'Type boeking is verplicht.';
124
- if (!v.bookingType2) e.bookingType2 = 'Type boeking is verplicht.';
125
-
126
- return e;
127
- };
128
-
129
- const currentErrors = useMemo(() => validate(values), [values]);
130
-
131
- const setField = <K extends keyof ContactValues>(key: K, val: ContactValues[K]) => {
132
- setValues((p) => ({ ...p, [key]: val }));
133
- if (submitted || touched[key]) {
134
- setErrors(validate({ ...values, [key]: val } as ContactValues));
135
- }
136
- };
137
-
138
- const onBlur = (key: keyof ContactValues) => {
139
- setTouched((p) => ({ ...p, [key]: true }));
140
- setErrors(validate(values));
141
- };
142
-
143
- const onSubmit = (e: React.FormEvent) => {
144
- e.preventDefault();
145
- setSubmitted(true);
146
-
147
- const eMap = validate(values);
148
- setErrors(eMap);
149
-
150
- if (Object.keys(eMap).length > 0) return;
151
-
152
- console.log('Contact form submit:', values);
153
-
154
- setValues({
155
- firstName: '',
156
- lastName: '',
157
- dateOfBirth: '',
158
- nationality: '',
159
- email: '',
160
- phone: '',
161
- message: '',
162
- street: '',
163
- houseNumber: '',
164
- box: '',
165
- postalCode: '',
166
- city: '',
167
- country: '',
168
- bookingType1: '',
169
- gender1: '',
170
- bookingType2: '',
171
- gender2: ''
172
- });
173
- setTouched({});
174
- setErrors({});
175
- setSubmitted(false);
176
- };
177
-
178
- const showError = (key: keyof ContactValues) => {
179
- const shouldShow = submitted || touched[key];
180
- return shouldShow ? errors[key] : undefined;
181
- };
182
-
183
- return (
184
- <div className="content content--background">
185
- <div className="content__container content__container--medium">
186
- <div className="content__title__row">
187
- <h2 className="content__title">Persoonlijke informatie</h2>
188
- </div>
189
-
190
- <form className="contact" noValidate onSubmit={onSubmit}>
191
- <div className="contact__card contact__card--headbooker">
192
- <div className="contact__title">
193
- <Icon name="ui-info-circle" width={24} height={24} />
194
- <h4>Gaat de hoofdboeker mee op reis?</h4>
195
- </div>
196
-
197
- <div className="contact__form">
198
- <div className="radiobutton-group">
199
- <div className="radiobutton">
200
- <label className="radiobutton__label">
201
- <input type="radio" name="headBooker" value="yes" className="radiobutton__input" defaultChecked={true} />
202
- <span>Ja</span>
203
- </label>
204
- </div>
205
-
206
- <div className="radiobutton">
207
- <label className="radiobutton__label">
208
- <input type="radio" name="headBooker" value="no" className="radiobutton__input" />
209
- <span>Nee</span>
210
- </label>
211
- </div>
212
- </div>
213
- </div>
214
- </div>
215
- <div className="contact__card">
216
- <div className="contact__title">
217
- <Icon name="ui-user" width={24} height={24} />
218
- <h4>Reiziger 1</h4>
219
- <span>volwassenen, hoofdboeker</span>
220
- </div>
221
-
222
- <div
223
- className="contact__radio"
224
- role="group"
225
- aria-labelledby="bookingType-label"
226
- aria-invalid={!!showError('bookingType1')}
227
- aria-describedby="bookingType-error">
228
- <span id="bookingType-label">Type boeking{/** add * if required */}</span>
229
-
230
- <div className="radiobutton-group">
231
- <div className="radiobutton">
232
- <label className="radiobutton__label">
233
- <input
234
- type="radio"
235
- name="bookingType1"
236
- value="leisure"
237
- className="radiobutton__input"
238
- checked={values.bookingType1 === 'leisure'}
239
- onChange={() => setField('bookingType1', 'leisure')}
240
- onBlur={() => onBlur('bookingType1')}
241
- />
242
- <span>Plezier</span>
243
- </label>
244
- </div>
245
-
246
- <div className="radiobutton">
247
- <label className="radiobutton__label">
248
- <input
249
- type="radio"
250
- name="bookingType1"
251
- value="business"
252
- className="radiobutton__input"
253
- checked={values.bookingType1 === 'business'}
254
- onChange={() => setField('bookingType1', 'business')}
255
- onBlur={() => onBlur('bookingType1')}
256
- />
257
- <span>Zakelijk</span>
258
- </label>
259
- </div>
260
- </div>
261
-
262
- {showError('bookingType1') && (
263
- <span className="contact__radio--error" id="bookingType-error">
264
- {showError('bookingType1')}
265
- </span>
266
- )}
267
- </div>
268
-
269
- <div className="contact__radio" role="group" aria-labelledby="gender-label" aria-invalid={!!showError('gender1')} aria-describedby="gender-error">
270
- <span id="gender-label">Geslacht*</span>
271
-
272
- <div className="radiobutton-group">
273
- <div className="radiobutton">
274
- <label className="radiobutton__label">
275
- <input
276
- type="radio"
277
- name="gender1"
278
- value="female"
279
- className="radiobutton__input"
280
- checked={values.gender1 === 'female'}
281
- onChange={() => setField('gender1', 'female')}
282
- onBlur={() => onBlur('gender1')}
283
- />
284
- <span>Vrouw</span>
285
- </label>
286
- </div>
287
-
288
- <div className="radiobutton">
289
- <label className="radiobutton__label">
290
- <input
291
- type="radio"
292
- name="gender1"
293
- value="male"
294
- className="radiobutton__input"
295
- checked={values.gender1 === 'male'}
296
- onChange={() => setField('gender1', 'male')}
297
- onBlur={() => onBlur('gender1')}
298
- />
299
- <span>Man</span>
300
- </label>
301
- </div>
302
-
303
- <div className="radiobutton">
304
- <label className="radiobutton__label">
305
- <input
306
- type="radio"
307
- name="gender1"
308
- value="other"
309
- className="radiobutton__input"
310
- checked={values.gender1 === 'other'}
311
- onChange={() => setField('gender1', 'other')}
312
- onBlur={() => onBlur('gender1')}
313
- />
314
- <span>Anders</span>
315
- </label>
316
- </div>
317
- </div>
318
-
319
- {showError('gender1') && (
320
- <span className="contact__radio--error" id="gender-error">
321
- {showError('gender1')}
322
- </span>
323
- )}
324
- </div>
325
-
326
- <div className="contact__form">
327
- <label className="contact__form__group contact__form__group--3">
328
- <span className="contact__form__label">Voornaam*</span>
329
- <input
330
- type="text"
331
- className="contact__input"
332
- placeholder="Enter your name"
333
- aria-label="Enter your name"
334
- value={values.firstName}
335
- onChange={(e) => setField('firstName', e.target.value)}
336
- onBlur={() => onBlur('firstName')}
337
- aria-invalid={!!showError('firstName')}
338
- aria-describedby="firstName-error"
339
- />
340
- {showError('firstName') && (
341
- <span className="contact__error" id="firstName-error">
342
- {showError('firstName')}
343
- </span>
344
- )}
345
- </label>
346
-
347
- <label className="contact__form__group contact__form__group--3">
348
- <span className="contact__form__label">Achternaam*</span>
349
- <input
350
- type="text"
351
- className="contact__input"
352
- placeholder="Enter your last name"
353
- aria-label="Enter your last name"
354
- value={values.lastName}
355
- onChange={(e) => setField('lastName', e.target.value)}
356
- onBlur={() => onBlur('lastName')}
357
- aria-invalid={!!showError('lastName')}
358
- aria-describedby="lastName-error"
359
- />
360
- {showError('lastName') && (
361
- <span className="contact__error" id="lastName-error">
362
- {showError('lastName')}
363
- </span>
364
- )}
365
- </label>
366
-
367
- <label className="contact__form__group contact__form__group--3">
368
- <span className="contact__form__label">Geboortedatum*</span>
369
- <input
370
- type="date"
371
- className="contact__input"
372
- aria-label="Enter your date of birth"
373
- value={values.dateOfBirth}
374
- onChange={(e) => setField('dateOfBirth', e.target.value)}
375
- onBlur={() => onBlur('dateOfBirth')}
376
- aria-invalid={!!showError('dateOfBirth')}
377
- aria-describedby="dateOfBirth-error"
378
- />
379
- {showError('dateOfBirth') && (
380
- <span className="contact__error" id="dateOfBirth-error">
381
- {showError('dateOfBirth')}
382
- </span>
383
- )}
384
- </label>
385
-
386
- <label className="contact__form__group contact__form__group--3">
387
- <span className="contact__form__label">Nationaliteit*</span>
388
-
389
- <div className={`contact__dropdown ${showError('nationality') ? 'contact__dropdown--error' : ''}`}>
390
- <button
391
- type="button"
392
- className="contact__dropdown__trigger"
393
- aria-haspopup="listbox"
394
- aria-expanded={isNationalityOpen}
395
- onClick={() => setIsNationalityOpen((o) => !o)}
396
- onBlur={() => {
397
- setIsNationalityOpen(false);
398
- onBlur('nationality');
399
- }}>
400
- <span>{nationalityOptions.find((o) => o.value === values.nationality)?.label ?? 'Selecteer nationaliteit'}</span>
401
-
402
- <Icon name="ui-chevron" width={16} height={16} />
403
- </button>
404
-
405
- {isNationalityOpen && (
406
- <ul className="contact__dropdown__menu" role="listbox">
407
- {nationalityOptions.map((option) => (
408
- <li key={option.value}>
409
- <button
410
- type="button"
411
- className={`contact__dropdown__option ${values.nationality === option.value ? 'contact__dropdown__option--active' : ''}`}
412
- role="option"
413
- aria-selected={values.nationality === option.value}
414
- onMouseDown={() => {
415
- // onMouseDown prevents blur-before-click
416
- setField('nationality', option.value);
417
- setIsNationalityOpen(false);
418
- }}>
419
- {option.label}
420
- </button>
421
- </li>
422
- ))}
423
- </ul>
424
- )}
425
- </div>
426
-
427
- {showError('nationality') && (
428
- <span className="contact__error" id="nationality-error">
429
- {showError('nationality')}
430
- </span>
431
- )}
432
- </label>
433
-
434
- <label className="contact__form__group contact__form__group--3">
435
- <span className="contact__form__label">Email*</span>
436
- <input
437
- type="email"
438
- className="contact__input"
439
- placeholder="Enter your email"
440
- aria-label="Enter your email"
441
- value={values.email}
442
- onChange={(e) => setField('email', e.target.value)}
443
- onBlur={() => onBlur('email')}
444
- aria-invalid={!!showError('email')}
445
- aria-describedby="email-error"
446
- />
447
- {showError('email') && (
448
- <span className="contact__error" id="email-error">
449
- {showError('email')}
450
- </span>
451
- )}
452
- </label>
453
-
454
- <label className="contact__form__group contact__form__group--3">
455
- <span className="contact__form__label">Telefoonnummer</span>
456
- <input
457
- type="tel"
458
- className="contact__input"
459
- placeholder="Enter your phone number"
460
- aria-label="Enter your phone number"
461
- value={values.phone}
462
- onChange={(e) => setField('phone', e.target.value)}
463
- onBlur={() => onBlur('phone')}
464
- aria-invalid={!!showError('phone')}
465
- aria-describedby="phone-error"
466
- />
467
- {showError('phone') && (
468
- <span className="contact__error" id="phone-error">
469
- {showError('phone')}
470
- </span>
471
- )}
472
- </label>
473
-
474
- <label className="contact__form__group contact__form__group--2">
475
- <span className="contact__form__label">Straat*</span>
476
- <input
477
- type="text"
478
- className="contact__input"
479
- placeholder="Straatnaam"
480
- aria-label="Straat"
481
- value={values.street}
482
- onChange={(e) => setField('street', e.target.value)}
483
- onBlur={() => onBlur('street')}
484
- aria-invalid={!!showError('street')}
485
- aria-describedby="street-error"
486
- />
487
- {showError('street') && (
488
- <span className="contact__error" id="street-error">
489
- {showError('street')}
490
- </span>
491
- )}
492
- </label>
493
-
494
- <label className="contact__form__group contact__form__group--4">
495
- <span className="contact__form__label">Nummer*</span>
496
- <input
497
- type="text"
498
- className="contact__input"
499
- placeholder="Nr"
500
- aria-label="Huisnummer"
501
- value={values.houseNumber}
502
- onChange={(e) => setField('houseNumber', e.target.value)}
503
- onBlur={() => onBlur('houseNumber')}
504
- aria-invalid={!!showError('houseNumber')}
505
- aria-describedby="houseNumber-error"
506
- />
507
- {showError('houseNumber') && (
508
- <span className="contact__error" id="houseNumber-error">
509
- {showError('houseNumber')}
510
- </span>
511
- )}
512
- </label>
513
-
514
- <label className="contact__form__group contact__form__group--4">
515
- <span className="contact__form__label">Bus</span>
516
- <input
517
- type="text"
518
- className="contact__input"
519
- placeholder="Bus"
520
- aria-label="Bus"
521
- value={values.box}
522
- onChange={(e) => setField('box', e.target.value)}
523
- onBlur={() => onBlur('box')}
524
- aria-invalid={!!showError('box')}
525
- aria-describedby="box-error"
526
- />
527
- {showError('box') && (
528
- <span className="contact__error" id="box-error">
529
- {showError('box')}
530
- </span>
531
- )}
532
- </label>
533
-
534
- <label className="contact__form__group contact__form__group--3">
535
- <span className="contact__form__label">Postcode*</span>
536
- <input
537
- type="text"
538
- className="contact__input"
539
- placeholder="Postcode"
540
- aria-label="Postcode"
541
- value={values.postalCode}
542
- onChange={(e) => setField('postalCode', e.target.value)}
543
- onBlur={() => onBlur('postalCode')}
544
- aria-invalid={!!showError('postalCode')}
545
- aria-describedby="postalCode-error"
546
- />
547
- {showError('postalCode') && (
548
- <span className="contact__error" id="postalCode-error">
549
- {showError('postalCode')}
550
- </span>
551
- )}
552
- </label>
553
-
554
- <label className="contact__form__group contact__form__group--3">
555
- <span className="contact__form__label">Woonplaats*</span>
556
- <input
557
- type="text"
558
- className="contact__input"
559
- placeholder="Woonplaats"
560
- aria-label="Woonplaats"
561
- value={values.city}
562
- onChange={(e) => setField('city', e.target.value)}
563
- onBlur={() => onBlur('city')}
564
- aria-invalid={!!showError('city')}
565
- aria-describedby="city-error"
566
- />
567
- {showError('city') && (
568
- <span className="contact__error" id="city-error">
569
- {showError('city')}
570
- </span>
571
- )}
572
- </label>
573
-
574
- <label className="contact__form__group contact__form__group--3">
575
- <span className="contact__form__label">Land*</span>
576
-
577
- <div className={`contact__dropdown ${showError('country') ? 'contact__dropdown--error' : ''}`}>
578
- <button
579
- type="button"
580
- className="contact__dropdown__trigger"
581
- aria-haspopup="listbox"
582
- aria-expanded={isCountryOpen}
583
- aria-describedby="country-error"
584
- onClick={() => setIsCountryOpen((o) => !o)}
585
- onBlur={() => {
586
- setIsCountryOpen(false);
587
- onBlur('country');
588
- }}>
589
- <span>{countryOptions.find((o) => o.value === values.country)?.label ?? 'Selecteer land'}</span>
590
- <Icon name="ui-chevron" width={16} height={16} />
591
- </button>
592
-
593
- {isCountryOpen && (
594
- <ul className="contact__dropdown__menu" role="listbox">
595
- {countryOptions.map((option) => (
596
- <li key={option.value}>
597
- <button
598
- type="button"
599
- className={`contact__dropdown__option ${values.country === option.value ? 'contact__dropdown__option--active' : ''}`}
600
- role="option"
601
- aria-selected={values.country === option.value}
602
- onMouseDown={() => {
603
- // Prevent blur-before-click
604
- setField('country', option.value);
605
- setIsCountryOpen(false);
606
- }}>
607
- {option.label}
608
- </button>
609
- </li>
610
- ))}
611
- </ul>
612
- )}
613
- </div>
614
-
615
- {showError('country') && (
616
- <span className="contact__error" id="country-error">
617
- {showError('country')}
618
- </span>
619
- )}
620
- </label>
621
- </div>
622
- </div>
623
-
624
- <div className="contact__card">
625
- <div className="contact__title">
626
- <Icon name="ui-user" width={24} height={24} />
627
- <h4>Reiziger 2</h4>
628
- <span>volwassenen</span>
629
- </div>
630
-
631
- <div className="contact__radio" role="group" aria-labelledby="gender-label" aria-invalid={!!showError('gender1')} aria-describedby="gender-error">
632
- <span id="gender-label">Geslacht*</span>
633
-
634
- <div className="radiobutton-group">
635
- <div className="radiobutton">
636
- <label className="radiobutton__label">
637
- <input
638
- type="radio"
639
- name="gender2"
640
- value="female"
641
- className="radiobutton__input"
642
- checked={values.gender2 === 'female'}
643
- onChange={() => setField('gender2', 'female')}
644
- onBlur={() => onBlur('gender2')}
645
- />
646
- <span>Vrouw</span>
647
- </label>
648
- </div>
649
-
650
- <div className="radiobutton">
651
- <label className="radiobutton__label">
652
- <input
653
- type="radio"
654
- name="gender2"
655
- value="male"
656
- className="radiobutton__input"
657
- checked={values.gender2 === 'male'}
658
- onChange={() => setField('gender2', 'male')}
659
- onBlur={() => onBlur('gender2')}
660
- />
661
- <span>Man</span>
662
- </label>
663
- </div>
664
-
665
- <div className="radiobutton">
666
- <label className="radiobutton__label">
667
- <input
668
- type="radio"
669
- name="gender2"
670
- value="other"
671
- className="radiobutton__input"
672
- checked={values.gender2 === 'other'}
673
- onChange={() => setField('gender2', 'other')}
674
- onBlur={() => onBlur('gender2')}
675
- />
676
- <span>Anders</span>
677
- </label>
678
- </div>
679
- </div>
680
-
681
- {showError('gender2') && (
682
- <span className="contact__radio--error" id="gender-error">
683
- {showError('gender2')}
684
- </span>
685
- )}
686
- </div>
687
-
688
- <div className="contact__form">
689
- <label className="contact__form__group contact__form__group--2">
690
- <span className="contact__form__label">Voornaam*</span>
691
- <input
692
- type="text"
693
- className="contact__input"
694
- placeholder="Enter your name"
695
- aria-label="Enter your name"
696
- value={values.firstName}
697
- onChange={(e) => setField('firstName', e.target.value)}
698
- onBlur={() => onBlur('firstName')}
699
- aria-invalid={!!showError('firstName')}
700
- aria-describedby="firstName-error"
701
- />
702
- {showError('firstName') && (
703
- <span className="contact__error" id="firstName-error">
704
- {showError('firstName')}
705
- </span>
706
- )}
707
- </label>
708
-
709
- <label className="contact__form__group contact__form__group--2">
710
- <span className="contact__form__label">Achternaam*</span>
711
- <input
712
- type="text"
713
- className="contact__input"
714
- placeholder="Enter your last name"
715
- aria-label="Enter your last name"
716
- value={values.lastName}
717
- onChange={(e) => setField('lastName', e.target.value)}
718
- onBlur={() => onBlur('lastName')}
719
- aria-invalid={!!showError('lastName')}
720
- aria-describedby="lastName-error"
721
- />
722
- {showError('lastName') && (
723
- <span className="contact__error" id="lastName-error">
724
- {showError('lastName')}
725
- </span>
726
- )}
727
- </label>
728
-
729
- <label className="contact__form__group contact__form__group--2">
730
- <span className="contact__form__label">Geboortedatum*</span>
731
- <input
732
- type="date"
733
- className="contact__input"
734
- aria-label="Enter your date of birth"
735
- value={values.dateOfBirth}
736
- onChange={(e) => setField('dateOfBirth', e.target.value)}
737
- onBlur={() => onBlur('dateOfBirth')}
738
- aria-invalid={!!showError('dateOfBirth')}
739
- aria-describedby="dateOfBirth-error"
740
- />
741
- {showError('dateOfBirth') && (
742
- <span className="contact__error" id="dateOfBirth-error">
743
- {showError('dateOfBirth')}
744
- </span>
745
- )}
746
- </label>
747
-
748
- <label className="contact__form__group contact__form__group--2">
749
- <span className="contact__form__label">Nationaliteit*</span>
750
-
751
- <div className={`contact__dropdown ${showError('nationality') ? 'contact__dropdown--error' : ''}`}>
752
- <button
753
- type="button"
754
- className="contact__dropdown__trigger"
755
- aria-haspopup="listbox"
756
- aria-expanded={isNationalityOpen}
757
- onClick={() => setIsNationalityOpen((o) => !o)}
758
- onBlur={() => {
759
- setIsNationalityOpen(false);
760
- onBlur('nationality');
761
- }}>
762
- <span>{nationalityOptions.find((o) => o.value === values.nationality)?.label ?? 'Selecteer nationaliteit'}</span>
763
-
764
- <Icon name="ui-chevron" width={16} height={16} />
765
- </button>
766
-
767
- {isNationalityOpen && (
768
- <ul className="contact__dropdown__menu" role="listbox">
769
- {nationalityOptions.map((option) => (
770
- <li key={option.value}>
771
- <button
772
- type="button"
773
- className={`contact__dropdown__option ${values.nationality === option.value ? 'contact__dropdown__option--active' : ''}`}
774
- role="option"
775
- aria-selected={values.nationality === option.value}
776
- onMouseDown={() => {
777
- // onMouseDown prevents blur-before-click
778
- setField('nationality', option.value);
779
- setIsNationalityOpen(false);
780
- }}>
781
- {option.label}
782
- </button>
783
- </li>
784
- ))}
785
- </ul>
786
- )}
787
- </div>
788
-
789
- {showError('nationality') && (
790
- <span className="contact__error" id="nationality-error">
791
- {showError('nationality')}
792
- </span>
793
- )}
794
- </label>
795
- </div>
796
- </div>
797
-
798
- <div className="contact__form__actions">
799
- <button type="submit" className="cta cta--primary">
800
- Send message
801
- </button>
802
- </div>
803
- </form>
804
- </div>
805
- </div>
806
- );
807
- };
808
-
809
- export default PersonalContactForm;
1
+ import React, { useMemo, useState } from 'react';
2
+ import Icon from './icon';
3
+
4
+ type ContactValues = {
5
+ firstName: string;
6
+ lastName: string;
7
+ dateOfBirth: string;
8
+ nationality: string;
9
+ email: string;
10
+ phone: string;
11
+ message: string;
12
+ street: string;
13
+ houseNumber: string;
14
+ box: string;
15
+ postalCode: string;
16
+ city: string;
17
+ country: string;
18
+ // traveler 1
19
+ bookingType1: 'leisure' | 'business' | '';
20
+ gender1: 'female' | 'male' | 'other' | '';
21
+
22
+ // traveler 2
23
+ bookingType2: 'leisure' | 'business' | '';
24
+ gender2: 'female' | 'male' | 'other' | '';
25
+ };
26
+
27
+ type ContactErrors = Partial<Record<keyof ContactValues, string>>;
28
+ // const [touched, setTouched] = useState<Partial<Record<keyof ContactValues, boolean>>>({});
29
+
30
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
31
+ const phoneRegex = /^[+()\-.\s0-9]{8,20}$/;
32
+
33
+ // Small helper: checks YYYY-MM-DD parses to a real date
34
+ const isValidISODate = (value: string) => {
35
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
36
+ const d = new Date(value);
37
+ return !Number.isNaN(d.getTime()) && d.toISOString().slice(0, 10) === value;
38
+ };
39
+
40
+ // You can expand this list later (or load from API)
41
+ const nationalityOptions = [
42
+ { value: '', label: 'Selecteer nationaliteit' },
43
+ { value: 'BE', label: 'Belgisch' },
44
+ { value: 'NL', label: 'Nederlands' },
45
+ { value: 'FR', label: 'Frans' },
46
+ { value: 'DE', label: 'Duits' },
47
+ { value: 'UK', label: 'Brits' },
48
+ { value: 'US', label: 'Amerikaans' }
49
+ ];
50
+
51
+ const countryOptions = [
52
+ { value: '', label: 'Selecteer land' },
53
+ { value: 'BE', label: 'België' },
54
+ { value: 'NL', label: 'Nederland' },
55
+ { value: 'FR', label: 'Frankrijk' },
56
+ { value: 'DE', label: 'Duitsland' },
57
+ { value: 'LU', label: 'Luxemburg' },
58
+ { value: 'UK', label: 'Verenigd Koninkrijk' }
59
+ ];
60
+
61
+ const PersonalContactForm: React.FC = () => {
62
+ const [values, setValues] = useState<ContactValues>({
63
+ firstName: '',
64
+ lastName: '',
65
+ dateOfBirth: '',
66
+ nationality: '',
67
+ email: '',
68
+ phone: '',
69
+ message: '',
70
+ street: '',
71
+ houseNumber: '',
72
+ box: '',
73
+ postalCode: '',
74
+ city: '',
75
+ country: '',
76
+ bookingType1: '',
77
+ gender1: '',
78
+ bookingType2: '',
79
+ gender2: ''
80
+ });
81
+
82
+ const [touched, setTouched] = useState<Partial<Record<keyof ContactValues, boolean>>>({});
83
+ const [errors, setErrors] = useState<ContactErrors>({});
84
+ const [submitted, setSubmitted] = useState(false);
85
+ const [isNationalityOpen, setIsNationalityOpen] = useState(false);
86
+ const [isCountryOpen, setIsCountryOpen] = useState(false);
87
+
88
+ const postalCodeRegex = /^[0-9A-Za-z\s-]{3,10}$/;
89
+
90
+ const validate = (v: ContactValues): ContactErrors => {
91
+ const e: ContactErrors = {};
92
+
93
+ if (!v.firstName.trim()) e.firstName = 'Voornaam is verplicht.';
94
+ if (!v.lastName.trim()) e.lastName = 'Achternaam is verplicht.';
95
+
96
+ if (!v.dateOfBirth.trim()) e.dateOfBirth = 'Geboortedatum is verplicht.';
97
+ else if (!isValidISODate(v.dateOfBirth.trim())) e.dateOfBirth = 'Vul een geldige geboortedatum in.';
98
+
99
+ if (!v.nationality.trim()) e.nationality = 'Nationaliteit is verplicht.';
100
+
101
+ if (!v.email.trim()) e.email = 'Email is verplicht.';
102
+ else if (!emailRegex.test(v.email.trim())) e.email = 'Vul een geldig e-mailadres in.';
103
+
104
+ if (v.phone.trim() && !phoneRegex.test(v.phone.trim())) {
105
+ e.phone = 'Vul een geldig telefoonnummer in.';
106
+ }
107
+
108
+ if (!v.message.trim()) e.message = 'Bericht is verplicht.';
109
+ else if (v.message.trim().length < 10) e.message = 'Bericht moet minstens 10 tekens zijn.';
110
+
111
+ if (!v.street.trim()) e.street = 'Straat is verplicht.';
112
+ if (!v.houseNumber.trim()) e.houseNumber = 'Nummer is verplicht.';
113
+
114
+ if (!v.postalCode.trim()) e.postalCode = 'Postcode is verplicht.';
115
+ else if (!postalCodeRegex.test(v.postalCode.trim())) e.postalCode = 'Vul een geldige postcode in.';
116
+
117
+ if (!v.city.trim()) e.city = 'Woonplaats is verplicht.';
118
+ if (!v.country.trim()) e.country = 'Land is verplicht.';
119
+
120
+ if (!v.gender1) e.gender1 = 'Geslacht is verplicht.';
121
+ if (!v.gender2) e.gender2 = 'Geslacht is verplicht.';
122
+
123
+ if (!v.bookingType1) e.bookingType1 = 'Type boeking is verplicht.';
124
+ if (!v.bookingType2) e.bookingType2 = 'Type boeking is verplicht.';
125
+
126
+ return e;
127
+ };
128
+
129
+ const currentErrors = useMemo(() => validate(values), [values]);
130
+
131
+ const setField = <K extends keyof ContactValues>(key: K, val: ContactValues[K]) => {
132
+ setValues((p) => ({ ...p, [key]: val }));
133
+ if (submitted || touched[key]) {
134
+ setErrors(validate({ ...values, [key]: val } as ContactValues));
135
+ }
136
+ };
137
+
138
+ const onBlur = (key: keyof ContactValues) => {
139
+ setTouched((p) => ({ ...p, [key]: true }));
140
+ setErrors(validate(values));
141
+ };
142
+
143
+ const onSubmit = (e: React.FormEvent) => {
144
+ e.preventDefault();
145
+ setSubmitted(true);
146
+
147
+ const eMap = validate(values);
148
+ setErrors(eMap);
149
+
150
+ if (Object.keys(eMap).length > 0) return;
151
+
152
+ console.log('Contact form submit:', values);
153
+
154
+ setValues({
155
+ firstName: '',
156
+ lastName: '',
157
+ dateOfBirth: '',
158
+ nationality: '',
159
+ email: '',
160
+ phone: '',
161
+ message: '',
162
+ street: '',
163
+ houseNumber: '',
164
+ box: '',
165
+ postalCode: '',
166
+ city: '',
167
+ country: '',
168
+ bookingType1: '',
169
+ gender1: '',
170
+ bookingType2: '',
171
+ gender2: ''
172
+ });
173
+ setTouched({});
174
+ setErrors({});
175
+ setSubmitted(false);
176
+ };
177
+
178
+ const showError = (key: keyof ContactValues) => {
179
+ const shouldShow = submitted || touched[key];
180
+ return shouldShow ? errors[key] : undefined;
181
+ };
182
+
183
+ return (
184
+ <div className="content content--background">
185
+ <div className="content__container content__container--medium">
186
+ <div className="content__title__row">
187
+ <h2 className="content__title">Persoonlijke informatie</h2>
188
+ </div>
189
+
190
+ <form className="contact" noValidate onSubmit={onSubmit}>
191
+ <div className="contact__card contact__card--headbooker">
192
+ <div className="contact__title">
193
+ <Icon name="ui-info-circle" width={24} height={24} />
194
+ <h4>Gaat de hoofdboeker mee op reis?</h4>
195
+ </div>
196
+
197
+ <div className="contact__form">
198
+ <div className="radiobutton-group">
199
+ <div className="radiobutton">
200
+ <label className="radiobutton__label">
201
+ <input type="radio" name="headBooker" value="yes" className="radiobutton__input" defaultChecked={true} />
202
+ <span>Ja</span>
203
+ </label>
204
+ </div>
205
+
206
+ <div className="radiobutton">
207
+ <label className="radiobutton__label">
208
+ <input type="radio" name="headBooker" value="no" className="radiobutton__input" />
209
+ <span>Nee</span>
210
+ </label>
211
+ </div>
212
+ </div>
213
+ </div>
214
+ </div>
215
+ <div className="contact__card">
216
+ <div className="contact__title">
217
+ <Icon name="ui-user" width={24} height={24} />
218
+ <h4>Reiziger 1</h4>
219
+ <span>volwassenen, hoofdboeker</span>
220
+ </div>
221
+
222
+ <div
223
+ className="contact__radio"
224
+ role="group"
225
+ aria-labelledby="bookingType-label"
226
+ aria-invalid={!!showError('bookingType1')}
227
+ aria-describedby="bookingType-error">
228
+ <span id="bookingType-label">Type boeking{/** add * if required */}</span>
229
+
230
+ <div className="radiobutton-group">
231
+ <div className="radiobutton">
232
+ <label className="radiobutton__label">
233
+ <input
234
+ type="radio"
235
+ name="bookingType1"
236
+ value="leisure"
237
+ className="radiobutton__input"
238
+ checked={values.bookingType1 === 'leisure'}
239
+ onChange={() => setField('bookingType1', 'leisure')}
240
+ onBlur={() => onBlur('bookingType1')}
241
+ />
242
+ <span>Plezier</span>
243
+ </label>
244
+ </div>
245
+
246
+ <div className="radiobutton">
247
+ <label className="radiobutton__label">
248
+ <input
249
+ type="radio"
250
+ name="bookingType1"
251
+ value="business"
252
+ className="radiobutton__input"
253
+ checked={values.bookingType1 === 'business'}
254
+ onChange={() => setField('bookingType1', 'business')}
255
+ onBlur={() => onBlur('bookingType1')}
256
+ />
257
+ <span>Zakelijk</span>
258
+ </label>
259
+ </div>
260
+ </div>
261
+
262
+ {showError('bookingType1') && (
263
+ <span className="contact__radio--error" id="bookingType-error">
264
+ {showError('bookingType1')}
265
+ </span>
266
+ )}
267
+ </div>
268
+
269
+ <div className="contact__radio" role="group" aria-labelledby="gender-label" aria-invalid={!!showError('gender1')} aria-describedby="gender-error">
270
+ <span id="gender-label">Geslacht*</span>
271
+
272
+ <div className="radiobutton-group">
273
+ <div className="radiobutton">
274
+ <label className="radiobutton__label">
275
+ <input
276
+ type="radio"
277
+ name="gender1"
278
+ value="female"
279
+ className="radiobutton__input"
280
+ checked={values.gender1 === 'female'}
281
+ onChange={() => setField('gender1', 'female')}
282
+ onBlur={() => onBlur('gender1')}
283
+ />
284
+ <span>Vrouw</span>
285
+ </label>
286
+ </div>
287
+
288
+ <div className="radiobutton">
289
+ <label className="radiobutton__label">
290
+ <input
291
+ type="radio"
292
+ name="gender1"
293
+ value="male"
294
+ className="radiobutton__input"
295
+ checked={values.gender1 === 'male'}
296
+ onChange={() => setField('gender1', 'male')}
297
+ onBlur={() => onBlur('gender1')}
298
+ />
299
+ <span>Man</span>
300
+ </label>
301
+ </div>
302
+
303
+ <div className="radiobutton">
304
+ <label className="radiobutton__label">
305
+ <input
306
+ type="radio"
307
+ name="gender1"
308
+ value="other"
309
+ className="radiobutton__input"
310
+ checked={values.gender1 === 'other'}
311
+ onChange={() => setField('gender1', 'other')}
312
+ onBlur={() => onBlur('gender1')}
313
+ />
314
+ <span>Anders</span>
315
+ </label>
316
+ </div>
317
+ </div>
318
+
319
+ {showError('gender1') && (
320
+ <span className="contact__radio--error" id="gender-error">
321
+ {showError('gender1')}
322
+ </span>
323
+ )}
324
+ </div>
325
+
326
+ <div className="contact__form">
327
+ <label className="contact__form__group contact__form__group--3">
328
+ <span className="contact__form__label">Voornaam*</span>
329
+ <input
330
+ type="text"
331
+ className="contact__input"
332
+ placeholder="Enter your name"
333
+ aria-label="Enter your name"
334
+ value={values.firstName}
335
+ onChange={(e) => setField('firstName', e.target.value)}
336
+ onBlur={() => onBlur('firstName')}
337
+ aria-invalid={!!showError('firstName')}
338
+ aria-describedby="firstName-error"
339
+ />
340
+ {showError('firstName') && (
341
+ <span className="contact__error" id="firstName-error">
342
+ {showError('firstName')}
343
+ </span>
344
+ )}
345
+ </label>
346
+
347
+ <label className="contact__form__group contact__form__group--3">
348
+ <span className="contact__form__label">Achternaam*</span>
349
+ <input
350
+ type="text"
351
+ className="contact__input"
352
+ placeholder="Enter your last name"
353
+ aria-label="Enter your last name"
354
+ value={values.lastName}
355
+ onChange={(e) => setField('lastName', e.target.value)}
356
+ onBlur={() => onBlur('lastName')}
357
+ aria-invalid={!!showError('lastName')}
358
+ aria-describedby="lastName-error"
359
+ />
360
+ {showError('lastName') && (
361
+ <span className="contact__error" id="lastName-error">
362
+ {showError('lastName')}
363
+ </span>
364
+ )}
365
+ </label>
366
+
367
+ <label className="contact__form__group contact__form__group--3">
368
+ <span className="contact__form__label">Geboortedatum*</span>
369
+ <input
370
+ type="date"
371
+ className="contact__input"
372
+ aria-label="Enter your date of birth"
373
+ value={values.dateOfBirth}
374
+ onChange={(e) => setField('dateOfBirth', e.target.value)}
375
+ onBlur={() => onBlur('dateOfBirth')}
376
+ aria-invalid={!!showError('dateOfBirth')}
377
+ aria-describedby="dateOfBirth-error"
378
+ />
379
+ {showError('dateOfBirth') && (
380
+ <span className="contact__error" id="dateOfBirth-error">
381
+ {showError('dateOfBirth')}
382
+ </span>
383
+ )}
384
+ </label>
385
+
386
+ <label className="contact__form__group contact__form__group--3">
387
+ <span className="contact__form__label">Nationaliteit*</span>
388
+
389
+ <div className={`contact__dropdown ${showError('nationality') ? 'contact__dropdown--error' : ''}`}>
390
+ <button
391
+ type="button"
392
+ className="contact__dropdown__trigger"
393
+ aria-haspopup="listbox"
394
+ aria-expanded={isNationalityOpen}
395
+ onClick={() => setIsNationalityOpen((o) => !o)}
396
+ onBlur={() => {
397
+ setIsNationalityOpen(false);
398
+ onBlur('nationality');
399
+ }}>
400
+ <span>{nationalityOptions.find((o) => o.value === values.nationality)?.label ?? 'Selecteer nationaliteit'}</span>
401
+
402
+ <Icon name="ui-chevron" width={16} height={16} />
403
+ </button>
404
+
405
+ {isNationalityOpen && (
406
+ <ul className="contact__dropdown__menu" role="listbox">
407
+ {nationalityOptions.map((option) => (
408
+ <li key={option.value}>
409
+ <button
410
+ type="button"
411
+ className={`contact__dropdown__option ${values.nationality === option.value ? 'contact__dropdown__option--active' : ''}`}
412
+ role="option"
413
+ aria-selected={values.nationality === option.value}
414
+ onMouseDown={() => {
415
+ // onMouseDown prevents blur-before-click
416
+ setField('nationality', option.value);
417
+ setIsNationalityOpen(false);
418
+ }}>
419
+ {option.label}
420
+ </button>
421
+ </li>
422
+ ))}
423
+ </ul>
424
+ )}
425
+ </div>
426
+
427
+ {showError('nationality') && (
428
+ <span className="contact__error" id="nationality-error">
429
+ {showError('nationality')}
430
+ </span>
431
+ )}
432
+ </label>
433
+
434
+ <label className="contact__form__group contact__form__group--3">
435
+ <span className="contact__form__label">Email*</span>
436
+ <input
437
+ type="email"
438
+ className="contact__input"
439
+ placeholder="Enter your email"
440
+ aria-label="Enter your email"
441
+ value={values.email}
442
+ onChange={(e) => setField('email', e.target.value)}
443
+ onBlur={() => onBlur('email')}
444
+ aria-invalid={!!showError('email')}
445
+ aria-describedby="email-error"
446
+ />
447
+ {showError('email') && (
448
+ <span className="contact__error" id="email-error">
449
+ {showError('email')}
450
+ </span>
451
+ )}
452
+ </label>
453
+
454
+ <label className="contact__form__group contact__form__group--3">
455
+ <span className="contact__form__label">Telefoonnummer</span>
456
+ <input
457
+ type="tel"
458
+ className="contact__input"
459
+ placeholder="Enter your phone number"
460
+ aria-label="Enter your phone number"
461
+ value={values.phone}
462
+ onChange={(e) => setField('phone', e.target.value)}
463
+ onBlur={() => onBlur('phone')}
464
+ aria-invalid={!!showError('phone')}
465
+ aria-describedby="phone-error"
466
+ />
467
+ {showError('phone') && (
468
+ <span className="contact__error" id="phone-error">
469
+ {showError('phone')}
470
+ </span>
471
+ )}
472
+ </label>
473
+
474
+ <label className="contact__form__group contact__form__group--2">
475
+ <span className="contact__form__label">Straat*</span>
476
+ <input
477
+ type="text"
478
+ className="contact__input"
479
+ placeholder="Straatnaam"
480
+ aria-label="Straat"
481
+ value={values.street}
482
+ onChange={(e) => setField('street', e.target.value)}
483
+ onBlur={() => onBlur('street')}
484
+ aria-invalid={!!showError('street')}
485
+ aria-describedby="street-error"
486
+ />
487
+ {showError('street') && (
488
+ <span className="contact__error" id="street-error">
489
+ {showError('street')}
490
+ </span>
491
+ )}
492
+ </label>
493
+
494
+ <label className="contact__form__group contact__form__group--4">
495
+ <span className="contact__form__label">Nummer*</span>
496
+ <input
497
+ type="text"
498
+ className="contact__input"
499
+ placeholder="Nr"
500
+ aria-label="Huisnummer"
501
+ value={values.houseNumber}
502
+ onChange={(e) => setField('houseNumber', e.target.value)}
503
+ onBlur={() => onBlur('houseNumber')}
504
+ aria-invalid={!!showError('houseNumber')}
505
+ aria-describedby="houseNumber-error"
506
+ />
507
+ {showError('houseNumber') && (
508
+ <span className="contact__error" id="houseNumber-error">
509
+ {showError('houseNumber')}
510
+ </span>
511
+ )}
512
+ </label>
513
+
514
+ <label className="contact__form__group contact__form__group--4">
515
+ <span className="contact__form__label">Bus</span>
516
+ <input
517
+ type="text"
518
+ className="contact__input"
519
+ placeholder="Bus"
520
+ aria-label="Bus"
521
+ value={values.box}
522
+ onChange={(e) => setField('box', e.target.value)}
523
+ onBlur={() => onBlur('box')}
524
+ aria-invalid={!!showError('box')}
525
+ aria-describedby="box-error"
526
+ />
527
+ {showError('box') && (
528
+ <span className="contact__error" id="box-error">
529
+ {showError('box')}
530
+ </span>
531
+ )}
532
+ </label>
533
+
534
+ <label className="contact__form__group contact__form__group--3">
535
+ <span className="contact__form__label">Postcode*</span>
536
+ <input
537
+ type="text"
538
+ className="contact__input"
539
+ placeholder="Postcode"
540
+ aria-label="Postcode"
541
+ value={values.postalCode}
542
+ onChange={(e) => setField('postalCode', e.target.value)}
543
+ onBlur={() => onBlur('postalCode')}
544
+ aria-invalid={!!showError('postalCode')}
545
+ aria-describedby="postalCode-error"
546
+ />
547
+ {showError('postalCode') && (
548
+ <span className="contact__error" id="postalCode-error">
549
+ {showError('postalCode')}
550
+ </span>
551
+ )}
552
+ </label>
553
+
554
+ <label className="contact__form__group contact__form__group--3">
555
+ <span className="contact__form__label">Woonplaats*</span>
556
+ <input
557
+ type="text"
558
+ className="contact__input"
559
+ placeholder="Woonplaats"
560
+ aria-label="Woonplaats"
561
+ value={values.city}
562
+ onChange={(e) => setField('city', e.target.value)}
563
+ onBlur={() => onBlur('city')}
564
+ aria-invalid={!!showError('city')}
565
+ aria-describedby="city-error"
566
+ />
567
+ {showError('city') && (
568
+ <span className="contact__error" id="city-error">
569
+ {showError('city')}
570
+ </span>
571
+ )}
572
+ </label>
573
+
574
+ <label className="contact__form__group contact__form__group--3">
575
+ <span className="contact__form__label">Land*</span>
576
+
577
+ <div className={`contact__dropdown ${showError('country') ? 'contact__dropdown--error' : ''}`}>
578
+ <button
579
+ type="button"
580
+ className="contact__dropdown__trigger"
581
+ aria-haspopup="listbox"
582
+ aria-expanded={isCountryOpen}
583
+ aria-describedby="country-error"
584
+ onClick={() => setIsCountryOpen((o) => !o)}
585
+ onBlur={() => {
586
+ setIsCountryOpen(false);
587
+ onBlur('country');
588
+ }}>
589
+ <span>{countryOptions.find((o) => o.value === values.country)?.label ?? 'Selecteer land'}</span>
590
+ <Icon name="ui-chevron" width={16} height={16} />
591
+ </button>
592
+
593
+ {isCountryOpen && (
594
+ <ul className="contact__dropdown__menu" role="listbox">
595
+ {countryOptions.map((option) => (
596
+ <li key={option.value}>
597
+ <button
598
+ type="button"
599
+ className={`contact__dropdown__option ${values.country === option.value ? 'contact__dropdown__option--active' : ''}`}
600
+ role="option"
601
+ aria-selected={values.country === option.value}
602
+ onMouseDown={() => {
603
+ // Prevent blur-before-click
604
+ setField('country', option.value);
605
+ setIsCountryOpen(false);
606
+ }}>
607
+ {option.label}
608
+ </button>
609
+ </li>
610
+ ))}
611
+ </ul>
612
+ )}
613
+ </div>
614
+
615
+ {showError('country') && (
616
+ <span className="contact__error" id="country-error">
617
+ {showError('country')}
618
+ </span>
619
+ )}
620
+ </label>
621
+ </div>
622
+ </div>
623
+
624
+ <div className="contact__card">
625
+ <div className="contact__title">
626
+ <Icon name="ui-user" width={24} height={24} />
627
+ <h4>Reiziger 2</h4>
628
+ <span>volwassenen</span>
629
+ </div>
630
+
631
+ <div className="contact__radio" role="group" aria-labelledby="gender-label" aria-invalid={!!showError('gender1')} aria-describedby="gender-error">
632
+ <span id="gender-label">Geslacht*</span>
633
+
634
+ <div className="radiobutton-group">
635
+ <div className="radiobutton">
636
+ <label className="radiobutton__label">
637
+ <input
638
+ type="radio"
639
+ name="gender2"
640
+ value="female"
641
+ className="radiobutton__input"
642
+ checked={values.gender2 === 'female'}
643
+ onChange={() => setField('gender2', 'female')}
644
+ onBlur={() => onBlur('gender2')}
645
+ />
646
+ <span>Vrouw</span>
647
+ </label>
648
+ </div>
649
+
650
+ <div className="radiobutton">
651
+ <label className="radiobutton__label">
652
+ <input
653
+ type="radio"
654
+ name="gender2"
655
+ value="male"
656
+ className="radiobutton__input"
657
+ checked={values.gender2 === 'male'}
658
+ onChange={() => setField('gender2', 'male')}
659
+ onBlur={() => onBlur('gender2')}
660
+ />
661
+ <span>Man</span>
662
+ </label>
663
+ </div>
664
+
665
+ <div className="radiobutton">
666
+ <label className="radiobutton__label">
667
+ <input
668
+ type="radio"
669
+ name="gender2"
670
+ value="other"
671
+ className="radiobutton__input"
672
+ checked={values.gender2 === 'other'}
673
+ onChange={() => setField('gender2', 'other')}
674
+ onBlur={() => onBlur('gender2')}
675
+ />
676
+ <span>Anders</span>
677
+ </label>
678
+ </div>
679
+ </div>
680
+
681
+ {showError('gender2') && (
682
+ <span className="contact__radio--error" id="gender-error">
683
+ {showError('gender2')}
684
+ </span>
685
+ )}
686
+ </div>
687
+
688
+ <div className="contact__form">
689
+ <label className="contact__form__group contact__form__group--2">
690
+ <span className="contact__form__label">Voornaam*</span>
691
+ <input
692
+ type="text"
693
+ className="contact__input"
694
+ placeholder="Enter your name"
695
+ aria-label="Enter your name"
696
+ value={values.firstName}
697
+ onChange={(e) => setField('firstName', e.target.value)}
698
+ onBlur={() => onBlur('firstName')}
699
+ aria-invalid={!!showError('firstName')}
700
+ aria-describedby="firstName-error"
701
+ />
702
+ {showError('firstName') && (
703
+ <span className="contact__error" id="firstName-error">
704
+ {showError('firstName')}
705
+ </span>
706
+ )}
707
+ </label>
708
+
709
+ <label className="contact__form__group contact__form__group--2">
710
+ <span className="contact__form__label">Achternaam*</span>
711
+ <input
712
+ type="text"
713
+ className="contact__input"
714
+ placeholder="Enter your last name"
715
+ aria-label="Enter your last name"
716
+ value={values.lastName}
717
+ onChange={(e) => setField('lastName', e.target.value)}
718
+ onBlur={() => onBlur('lastName')}
719
+ aria-invalid={!!showError('lastName')}
720
+ aria-describedby="lastName-error"
721
+ />
722
+ {showError('lastName') && (
723
+ <span className="contact__error" id="lastName-error">
724
+ {showError('lastName')}
725
+ </span>
726
+ )}
727
+ </label>
728
+
729
+ <label className="contact__form__group contact__form__group--2">
730
+ <span className="contact__form__label">Geboortedatum*</span>
731
+ <input
732
+ type="date"
733
+ className="contact__input"
734
+ aria-label="Enter your date of birth"
735
+ value={values.dateOfBirth}
736
+ onChange={(e) => setField('dateOfBirth', e.target.value)}
737
+ onBlur={() => onBlur('dateOfBirth')}
738
+ aria-invalid={!!showError('dateOfBirth')}
739
+ aria-describedby="dateOfBirth-error"
740
+ />
741
+ {showError('dateOfBirth') && (
742
+ <span className="contact__error" id="dateOfBirth-error">
743
+ {showError('dateOfBirth')}
744
+ </span>
745
+ )}
746
+ </label>
747
+
748
+ <label className="contact__form__group contact__form__group--2">
749
+ <span className="contact__form__label">Nationaliteit*</span>
750
+
751
+ <div className={`contact__dropdown ${showError('nationality') ? 'contact__dropdown--error' : ''}`}>
752
+ <button
753
+ type="button"
754
+ className="contact__dropdown__trigger"
755
+ aria-haspopup="listbox"
756
+ aria-expanded={isNationalityOpen}
757
+ onClick={() => setIsNationalityOpen((o) => !o)}
758
+ onBlur={() => {
759
+ setIsNationalityOpen(false);
760
+ onBlur('nationality');
761
+ }}>
762
+ <span>{nationalityOptions.find((o) => o.value === values.nationality)?.label ?? 'Selecteer nationaliteit'}</span>
763
+
764
+ <Icon name="ui-chevron" width={16} height={16} />
765
+ </button>
766
+
767
+ {isNationalityOpen && (
768
+ <ul className="contact__dropdown__menu" role="listbox">
769
+ {nationalityOptions.map((option) => (
770
+ <li key={option.value}>
771
+ <button
772
+ type="button"
773
+ className={`contact__dropdown__option ${values.nationality === option.value ? 'contact__dropdown__option--active' : ''}`}
774
+ role="option"
775
+ aria-selected={values.nationality === option.value}
776
+ onMouseDown={() => {
777
+ // onMouseDown prevents blur-before-click
778
+ setField('nationality', option.value);
779
+ setIsNationalityOpen(false);
780
+ }}>
781
+ {option.label}
782
+ </button>
783
+ </li>
784
+ ))}
785
+ </ul>
786
+ )}
787
+ </div>
788
+
789
+ {showError('nationality') && (
790
+ <span className="contact__error" id="nationality-error">
791
+ {showError('nationality')}
792
+ </span>
793
+ )}
794
+ </label>
795
+ </div>
796
+ </div>
797
+
798
+ <div className="contact__form__actions">
799
+ <button type="submit" className="cta cta--primary">
800
+ Send message
801
+ </button>
802
+ </div>
803
+ </form>
804
+ </div>
805
+ </div>
806
+ );
807
+ };
808
+
809
+ export default PersonalContactForm;