@qite/tide-booking-component 1.4.93 → 1.4.95
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.
- package/.prettierrc +9 -9
- package/.vs/ProjectSettings.json +3 -3
- package/.vs/VSWorkspaceState.json +5 -5
- package/build/build-cjs/index.js +81 -27
- package/build/build-cjs/src/booking-wizard/features/booking/booking-slice.d.ts +2 -1
- package/build/build-cjs/src/booking-wizard/features/booking/selectors.d.ts +4 -3
- package/build/build-cjs/src/booking-wizard/features/price-details/price-details-slice.d.ts +1 -0
- package/build/build-cjs/src/booking-wizard/features/price-details/selectors.d.ts +1 -0
- package/build/build-cjs/src/booking-wizard/features/sidebar/sidebar.d.ts +1 -0
- package/build/build-cjs/src/booking-wizard/types.d.ts +1 -0
- package/build/build-cjs/src/shared/utils/localization-util.d.ts +1 -0
- package/build/build-esm/index.js +81 -27
- package/build/build-esm/src/booking-wizard/features/booking/booking-slice.d.ts +2 -1
- package/build/build-esm/src/booking-wizard/features/booking/selectors.d.ts +4 -3
- package/build/build-esm/src/booking-wizard/features/price-details/price-details-slice.d.ts +1 -0
- package/build/build-esm/src/booking-wizard/features/price-details/selectors.d.ts +1 -0
- package/build/build-esm/src/booking-wizard/features/sidebar/sidebar.d.ts +1 -0
- package/build/build-esm/src/booking-wizard/types.d.ts +1 -0
- package/build/build-esm/src/shared/utils/localization-util.d.ts +1 -0
- package/package.json +83 -83
- package/src/booking-product/components/age-select.tsx +35 -35
- package/src/booking-product/components/amount-input.tsx +51 -51
- package/src/booking-product/components/date-range-picker/calendar.tsx +155 -155
- package/src/booking-product/components/footer.tsx +54 -54
- package/src/booking-product/components/header.tsx +57 -57
- package/src/booking-product/components/icon.tsx +200 -200
- package/src/booking-product/components/list-view.tsx +54 -54
- package/src/booking-product/components/rating.tsx +21 -21
- package/src/booking-product/components/rooms.tsx +171 -171
- package/src/booking-product/constants.ts +1 -1
- package/src/booking-product/index.tsx +21 -21
- package/src/booking-product/settings-context.ts +16 -16
- package/src/booking-product/types.ts +30 -30
- package/src/booking-product/utils/api.ts +26 -26
- package/src/booking-product/utils/price.ts +28 -28
- package/src/booking-wizard/api-settings-slice.ts +24 -24
- package/src/booking-wizard/components/icon.tsx +398 -398
- package/src/booking-wizard/components/labeled-input.tsx +56 -56
- package/src/booking-wizard/components/labeled-select.tsx +54 -54
- package/src/booking-wizard/components/message.tsx +21 -21
- package/src/booking-wizard/components/multi-range-filter.tsx +99 -99
- package/src/booking-wizard/components/phone-input.tsx +146 -146
- package/src/booking-wizard/components/print-offer-button.tsx +53 -53
- package/src/booking-wizard/components/product-card.tsx +23 -23
- package/src/booking-wizard/declarations.d.ts +4 -4
- package/src/booking-wizard/features/booking/booking-self-contained.tsx +16 -1
- package/src/booking-wizard/features/booking/booking-slice.ts +9 -1
- package/src/booking-wizard/features/booking/booking.tsx +16 -1
- package/src/booking-wizard/features/booking/selectors.ts +5 -0
- package/src/booking-wizard/features/flight-options/flight-filter.tsx +371 -371
- package/src/booking-wizard/features/flight-options/flight-option-flight.tsx +354 -354
- package/src/booking-wizard/features/flight-options/flight-option-modal.tsx +211 -211
- package/src/booking-wizard/features/flight-options/flight-option.tsx +57 -57
- package/src/booking-wizard/features/flight-options/flight-utils.ts +423 -423
- package/src/booking-wizard/features/price-details/price-details-api.ts +20 -20
- package/src/booking-wizard/features/price-details/price-details-slice.ts +2 -0
- package/src/booking-wizard/features/price-details/selectors.ts +1 -0
- package/src/booking-wizard/features/price-details/util.ts +115 -115
- package/src/booking-wizard/features/product-options/no-options.tsx +18 -18
- package/src/booking-wizard/features/product-options/none-option.tsx +73 -73
- package/src/booking-wizard/features/product-options/option-booking-airline-group.tsx +53 -53
- package/src/booking-wizard/features/product-options/option-booking-group.tsx +152 -152
- package/src/booking-wizard/features/product-options/option-item.tsx +236 -236
- package/src/booking-wizard/features/product-options/option-pax-card.tsx +159 -159
- package/src/booking-wizard/features/product-options/option-pax-group.tsx +122 -122
- package/src/booking-wizard/features/product-options/option-room.tsx +226 -226
- package/src/booking-wizard/features/product-options/option-unit-group.tsx +138 -138
- package/src/booking-wizard/features/room-options/room-utils.ts +154 -154
- package/src/booking-wizard/features/room-options/room.tsx +123 -123
- package/src/booking-wizard/features/room-options/traveler-rooms.tsx +64 -64
- package/src/booking-wizard/features/sidebar/index.tsx +2 -0
- package/src/booking-wizard/features/sidebar/sidebar-flight.tsx +66 -66
- package/src/booking-wizard/features/sidebar/sidebar.tsx +17 -1
- package/src/booking-wizard/features/summary/summary-booking-option-pax.tsx +23 -23
- package/src/booking-wizard/features/summary/summary-booking-option-unit.tsx +23 -23
- package/src/booking-wizard/features/summary/summary-flight.tsx +36 -36
- package/src/booking-wizard/features/summary/summary-per-booking-option-group.tsx +60 -60
- package/src/booking-wizard/features/summary/summary-per-pax-option-group.tsx +56 -56
- package/src/booking-wizard/features/summary/summary-per-unit-option-group.tsx +58 -58
- package/src/booking-wizard/features/summary/summary-slice.ts +27 -27
- package/src/booking-wizard/features/travelers-form/travelers-form-slice.ts +157 -157
- package/src/booking-wizard/features/travelers-form/travelers-form-util.ts +10 -10
- package/src/booking-wizard/features/travelers-form/type-ahead-input.tsx +85 -85
- package/src/booking-wizard/features/travelers-form/validate-form.ts +178 -178
- package/src/booking-wizard/index.tsx +27 -27
- package/src/booking-wizard/store.ts +26 -26
- package/src/booking-wizard/types.ts +1 -0
- package/src/booking-wizard/use-offer-printer.ts +108 -108
- package/src/content/components/LanguageSwitcher.tsx +158 -158
- package/src/content/components/accordion.tsx +30 -30
- package/src/content/components/contact.tsx +211 -211
- package/src/content/components/personal-contact-form.tsx +809 -809
- package/src/content/header/index.tsx +43 -43
- package/src/content/header/types.ts +26 -26
- package/src/qsm/components/date-picker/index.tsx +152 -152
- package/src/qsm/components/date-range-picker/calendar-day.tsx +49 -49
- package/src/qsm/components/date-range-picker/calendar.tsx +211 -211
- package/src/qsm/components/date-range-picker/index.tsx +404 -404
- package/src/qsm/index.tsx +26 -26
- package/src/qsm/store/qsm-store.ts +13 -13
- package/src/search-results/components/flight/flight-card.tsx +38 -38
- package/src/search-results/components/flight/flight-leg.tsx +61 -61
- package/src/search-results/components/flight/flight-path.tsx +23 -23
- package/src/search-results/components/multi-range-filter.tsx +104 -104
- package/src/search-results/components/search-results-container/search-results-container.tsx +2 -2
- package/src/search-results/index.tsx +24 -24
- package/src/search-results/search-results-configuration-context.ts +6 -6
- package/src/search-results/store/search-results-store.ts +13 -13
- package/src/shared/components/loader.tsx +16 -16
- package/src/shared/translations/ar-SA.json +2 -1
- package/src/shared/translations/da-DK.json +2 -1
- package/src/shared/translations/de-DE.json +2 -1
- package/src/shared/translations/en-GB.json +2 -1
- package/src/shared/translations/es-ES.json +2 -1
- package/src/shared/translations/fr-BE.json +2 -1
- package/src/shared/translations/fr-FR.json +2 -1
- package/src/shared/translations/is-IS.json +2 -1
- package/src/shared/translations/it-IT.json +2 -1
- package/src/shared/translations/ja-JP.json +2 -1
- package/src/shared/translations/nl-BE.json +2 -1
- package/src/shared/translations/nl-NL.json +2 -1
- package/src/shared/translations/no-NO.json +2 -1
- package/src/shared/translations/pl-PL.json +2 -1
- package/src/shared/translations/pt-PT.json +2 -1
- package/src/shared/translations/sv-SE.json +2 -1
- package/src/shared/utils/class-util.ts +7 -7
- package/src/shared/utils/query-string-util.ts +91 -91
- package/src/shared/utils/tide-api-utils.ts +34 -34
- package/src/shared/utils/use-media-query-util.ts +19 -19
- package/styles/abstracts/_mixins.scss +74 -74
- package/styles/abstracts/_variables.scss +57 -57
- package/styles/base/_fonts.scss +2 -2
- package/styles/base/_normalize.scss +227 -227
- package/styles/base/_typography.scss +35 -35
- package/styles/booking-joker-variables.scss +596 -596
- package/styles/booking-product-variables.scss +330 -330
- package/styles/booking-product.scss +438 -438
- package/styles/booking-qsm-variables.scss +501 -501
- package/styles/booking-qsm.scss +52 -52
- package/styles/booking-wizard-variables.scss +603 -603
- package/styles/booking-wizard.scss +61 -61
- package/styles/components/_accordion.scss +67 -67
- package/styles/components/_animations.scss +39 -39
- package/styles/components/_base.scss +107 -107
- package/styles/components/_breadcrumb.scss +92 -92
- package/styles/components/_button.scss +238 -238
- package/styles/components/_checkbox.scss +230 -230
- package/styles/components/_contact.scss +239 -239
- package/styles/components/_cta.scss +238 -238
- package/styles/components/_date-list.scss +41 -41
- package/styles/components/_date-range-picker.scss +223 -223
- package/styles/components/_decrement-increment.scss +35 -35
- package/styles/components/_dropdown.scss +72 -72
- package/styles/components/_faq.scss +27 -27
- package/styles/components/_flight-option.scss +1419 -1419
- package/styles/components/_gallery.scss +314 -314
- package/styles/components/_header.scss +113 -113
- package/styles/components/_img-slider.scss +175 -175
- package/styles/components/_info-message.scss +75 -75
- package/styles/components/_input.scss +35 -35
- package/styles/components/_list.scss +185 -185
- package/styles/components/_loader.scss +70 -70
- package/styles/components/_mixins.scss +579 -579
- package/styles/components/_passenger-picker.scss +306 -306
- package/styles/components/_phone-input.scss +8 -8
- package/styles/components/_placeholders.scss +165 -165
- package/styles/components/_qsm.scss +17 -17
- package/styles/components/_radiobutton.scss +170 -170
- package/styles/components/_select-wrapper.scss +76 -76
- package/styles/components/_slider.scss +128 -128
- package/styles/components/_spinner.scss +29 -29
- package/styles/components/_step-indicators.scss +161 -161
- package/styles/components/_table.scss +81 -81
- package/styles/components/_typeahead.scss +275 -275
- package/styles/components/_variables.scss +89 -89
- package/styles/content-blocks-variables.scss +507 -507
- package/styles/font.scss +2 -2
- package/styles/qsm/_calendar.scss +274 -274
- package/styles/qsm/_qsm.scss +1094 -1094
- package/styles/search.scss +1200 -1200
- package/tsconfig.json +24 -24
|
@@ -1,211 +1,211 @@
|
|
|
1
|
-
import React, { useMemo, useState } from 'react';
|
|
2
|
-
import Icon from './icon';
|
|
3
|
-
|
|
4
|
-
type ContactValues = {
|
|
5
|
-
firstName: string;
|
|
6
|
-
lastName: string;
|
|
7
|
-
email: string;
|
|
8
|
-
phone: string;
|
|
9
|
-
message: string;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
type ContactErrors = Partial<Record<keyof ContactValues, string>>;
|
|
13
|
-
|
|
14
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
15
|
-
const phoneRegex = /^[+()\-.\s0-9]{8,20}$/;
|
|
16
|
-
|
|
17
|
-
const ContactForm: React.FC = () => {
|
|
18
|
-
const [values, setValues] = useState<ContactValues>({
|
|
19
|
-
firstName: '',
|
|
20
|
-
lastName: '',
|
|
21
|
-
email: '',
|
|
22
|
-
phone: '',
|
|
23
|
-
message: ''
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const [touched, setTouched] = useState<Partial<Record<keyof ContactValues, boolean>>>({});
|
|
27
|
-
const [errors, setErrors] = useState<ContactErrors>({});
|
|
28
|
-
const [submitted, setSubmitted] = useState(false);
|
|
29
|
-
|
|
30
|
-
const validate = (v: ContactValues): ContactErrors => {
|
|
31
|
-
const e: ContactErrors = {};
|
|
32
|
-
|
|
33
|
-
if (!v.firstName.trim()) e.firstName = 'Voornaam is verplicht.';
|
|
34
|
-
if (!v.lastName.trim()) e.lastName = 'Achternaam is verplicht.';
|
|
35
|
-
|
|
36
|
-
if (!v.email.trim()) e.email = 'Email is verplicht.';
|
|
37
|
-
else if (!emailRegex.test(v.email.trim())) e.email = 'Vul een geldig e-mailadres in.';
|
|
38
|
-
|
|
39
|
-
if (v.phone.trim() && !phoneRegex.test(v.phone.trim())) {
|
|
40
|
-
e.phone = 'Vul een geldig telefoonnummer in.';
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
if (!v.message.trim()) e.message = 'Bericht is verplicht.';
|
|
44
|
-
else if (v.message.trim().length < 10) e.message = 'Bericht moet minstens 10 tekens zijn.';
|
|
45
|
-
|
|
46
|
-
return e;
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const currentErrors = useMemo(() => validate(values), [values]);
|
|
50
|
-
|
|
51
|
-
const setField = <K extends keyof ContactValues>(key: K, val: ContactValues[K]) => {
|
|
52
|
-
setValues((p) => ({ ...p, [key]: val }));
|
|
53
|
-
if (submitted || touched[key]) {
|
|
54
|
-
setErrors(validate({ ...values, [key]: val } as ContactValues));
|
|
55
|
-
}
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
const onBlur = (key: keyof ContactValues) => {
|
|
59
|
-
setTouched((p) => ({ ...p, [key]: true }));
|
|
60
|
-
setErrors(validate(values));
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const onSubmit = (e: React.FormEvent) => {
|
|
64
|
-
e.preventDefault();
|
|
65
|
-
setSubmitted(true);
|
|
66
|
-
|
|
67
|
-
const eMap = validate(values);
|
|
68
|
-
setErrors(eMap);
|
|
69
|
-
|
|
70
|
-
if (Object.keys(eMap).length > 0) return;
|
|
71
|
-
|
|
72
|
-
console.log('Contact form submit:', values);
|
|
73
|
-
|
|
74
|
-
setValues({ firstName: '', lastName: '', email: '', phone: '', message: '' });
|
|
75
|
-
setTouched({});
|
|
76
|
-
setErrors({});
|
|
77
|
-
setSubmitted(false);
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
const showError = (key: keyof ContactValues) => {
|
|
81
|
-
const shouldShow = submitted || touched[key];
|
|
82
|
-
return shouldShow ? errors[key] : undefined;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
return (
|
|
86
|
-
<div className="content content--background">
|
|
87
|
-
<div className="content__container content__container--small">
|
|
88
|
-
<div className="content__title__row">
|
|
89
|
-
<h2 className="content__title">Contact form</h2>
|
|
90
|
-
</div>
|
|
91
|
-
|
|
92
|
-
<div className="contact">
|
|
93
|
-
<form className="contact__form" noValidate onSubmit={onSubmit}>
|
|
94
|
-
<label className="contact__form__group contact__form__group--2">
|
|
95
|
-
<span className="contact__form__label">Voornaam*</span>
|
|
96
|
-
<input
|
|
97
|
-
type="text"
|
|
98
|
-
className="contact__input"
|
|
99
|
-
placeholder="Enter your name"
|
|
100
|
-
aria-label="Enter your name"
|
|
101
|
-
value={values.firstName}
|
|
102
|
-
onChange={(e) => setField('firstName', e.target.value)}
|
|
103
|
-
onBlur={() => onBlur('firstName')}
|
|
104
|
-
aria-invalid={!!showError('firstName')}
|
|
105
|
-
aria-describedby="firstName-error"
|
|
106
|
-
/>
|
|
107
|
-
{showError('firstName') && (
|
|
108
|
-
<span className="contact__error" id="firstName-error">
|
|
109
|
-
{showError('firstName')}
|
|
110
|
-
</span>
|
|
111
|
-
)}
|
|
112
|
-
</label>
|
|
113
|
-
|
|
114
|
-
<label className="contact__form__group contact__form__group--2">
|
|
115
|
-
<span className="contact__form__label">Achternaam*</span>
|
|
116
|
-
<input
|
|
117
|
-
type="text"
|
|
118
|
-
className="contact__input"
|
|
119
|
-
placeholder="Enter your last name"
|
|
120
|
-
aria-label="Enter your last name"
|
|
121
|
-
value={values.lastName}
|
|
122
|
-
onChange={(e) => setField('lastName', e.target.value)}
|
|
123
|
-
onBlur={() => onBlur('lastName')}
|
|
124
|
-
aria-invalid={!!showError('lastName')}
|
|
125
|
-
aria-describedby="lastName-error"
|
|
126
|
-
/>
|
|
127
|
-
{showError('lastName') && (
|
|
128
|
-
<span className="contact__error" id="lastName-error">
|
|
129
|
-
{showError('lastName')}
|
|
130
|
-
</span>
|
|
131
|
-
)}
|
|
132
|
-
</label>
|
|
133
|
-
|
|
134
|
-
<label className="contact__form__group contact__form__group--2">
|
|
135
|
-
<span className="contact__form__label">Email*</span>
|
|
136
|
-
<input
|
|
137
|
-
type="email"
|
|
138
|
-
className="contact__input"
|
|
139
|
-
placeholder="Enter your email"
|
|
140
|
-
aria-label="Enter your email"
|
|
141
|
-
value={values.email}
|
|
142
|
-
onChange={(e) => setField('email', e.target.value)}
|
|
143
|
-
onBlur={() => onBlur('email')}
|
|
144
|
-
aria-invalid={!!showError('email')}
|
|
145
|
-
aria-describedby="email-error"
|
|
146
|
-
/>
|
|
147
|
-
{showError('email') && (
|
|
148
|
-
<span className="contact__error" id="email-error">
|
|
149
|
-
{showError('email')}
|
|
150
|
-
</span>
|
|
151
|
-
)}
|
|
152
|
-
</label>
|
|
153
|
-
|
|
154
|
-
<label className="contact__form__group contact__form__group--2">
|
|
155
|
-
<span className="contact__form__label">Telefoonnummer</span>
|
|
156
|
-
<input
|
|
157
|
-
type="tel"
|
|
158
|
-
className="contact__input"
|
|
159
|
-
placeholder="Enter your phone number"
|
|
160
|
-
aria-label="Enter your phone number"
|
|
161
|
-
value={values.phone}
|
|
162
|
-
onChange={(e) => setField('phone', e.target.value)}
|
|
163
|
-
onBlur={() => onBlur('phone')}
|
|
164
|
-
aria-invalid={!!showError('phone')}
|
|
165
|
-
aria-describedby="phone-error"
|
|
166
|
-
/>
|
|
167
|
-
{showError('phone') && (
|
|
168
|
-
<span className="contact__error" id="phone-error">
|
|
169
|
-
{showError('phone')}
|
|
170
|
-
</span>
|
|
171
|
-
)}
|
|
172
|
-
</label>
|
|
173
|
-
|
|
174
|
-
<label className="contact__form__group">
|
|
175
|
-
<span className="contact__form__label">Bericht*</span>
|
|
176
|
-
<textarea
|
|
177
|
-
className="contact__textarea"
|
|
178
|
-
placeholder="Enter your message"
|
|
179
|
-
aria-label="Enter your message"
|
|
180
|
-
value={values.message}
|
|
181
|
-
onChange={(e) => setField('message', e.target.value)}
|
|
182
|
-
onBlur={() => onBlur('message')}
|
|
183
|
-
aria-invalid={!!showError('message')}
|
|
184
|
-
aria-describedby="message-error"
|
|
185
|
-
/>
|
|
186
|
-
{showError('message') && (
|
|
187
|
-
<span className="contact__error" id="message-error">
|
|
188
|
-
{showError('message')}
|
|
189
|
-
</span>
|
|
190
|
-
)}
|
|
191
|
-
</label>
|
|
192
|
-
|
|
193
|
-
<div className="contact__form__actions">
|
|
194
|
-
<button type="submit" className="cta cta--primary">
|
|
195
|
-
Send message
|
|
196
|
-
</button>
|
|
197
|
-
</div>
|
|
198
|
-
{/*
|
|
199
|
-
{submitted && Object.keys(currentErrors).length > 0 && (
|
|
200
|
-
<div className="contact__error-summary" role="alert">
|
|
201
|
-
Controleer de velden in het formulier.
|
|
202
|
-
</div>
|
|
203
|
-
)} */}
|
|
204
|
-
</form>
|
|
205
|
-
</div>
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
);
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
export default ContactForm;
|
|
1
|
+
import React, { useMemo, useState } from 'react';
|
|
2
|
+
import Icon from './icon';
|
|
3
|
+
|
|
4
|
+
type ContactValues = {
|
|
5
|
+
firstName: string;
|
|
6
|
+
lastName: string;
|
|
7
|
+
email: string;
|
|
8
|
+
phone: string;
|
|
9
|
+
message: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
type ContactErrors = Partial<Record<keyof ContactValues, string>>;
|
|
13
|
+
|
|
14
|
+
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
15
|
+
const phoneRegex = /^[+()\-.\s0-9]{8,20}$/;
|
|
16
|
+
|
|
17
|
+
const ContactForm: React.FC = () => {
|
|
18
|
+
const [values, setValues] = useState<ContactValues>({
|
|
19
|
+
firstName: '',
|
|
20
|
+
lastName: '',
|
|
21
|
+
email: '',
|
|
22
|
+
phone: '',
|
|
23
|
+
message: ''
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const [touched, setTouched] = useState<Partial<Record<keyof ContactValues, boolean>>>({});
|
|
27
|
+
const [errors, setErrors] = useState<ContactErrors>({});
|
|
28
|
+
const [submitted, setSubmitted] = useState(false);
|
|
29
|
+
|
|
30
|
+
const validate = (v: ContactValues): ContactErrors => {
|
|
31
|
+
const e: ContactErrors = {};
|
|
32
|
+
|
|
33
|
+
if (!v.firstName.trim()) e.firstName = 'Voornaam is verplicht.';
|
|
34
|
+
if (!v.lastName.trim()) e.lastName = 'Achternaam is verplicht.';
|
|
35
|
+
|
|
36
|
+
if (!v.email.trim()) e.email = 'Email is verplicht.';
|
|
37
|
+
else if (!emailRegex.test(v.email.trim())) e.email = 'Vul een geldig e-mailadres in.';
|
|
38
|
+
|
|
39
|
+
if (v.phone.trim() && !phoneRegex.test(v.phone.trim())) {
|
|
40
|
+
e.phone = 'Vul een geldig telefoonnummer in.';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!v.message.trim()) e.message = 'Bericht is verplicht.';
|
|
44
|
+
else if (v.message.trim().length < 10) e.message = 'Bericht moet minstens 10 tekens zijn.';
|
|
45
|
+
|
|
46
|
+
return e;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const currentErrors = useMemo(() => validate(values), [values]);
|
|
50
|
+
|
|
51
|
+
const setField = <K extends keyof ContactValues>(key: K, val: ContactValues[K]) => {
|
|
52
|
+
setValues((p) => ({ ...p, [key]: val }));
|
|
53
|
+
if (submitted || touched[key]) {
|
|
54
|
+
setErrors(validate({ ...values, [key]: val } as ContactValues));
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const onBlur = (key: keyof ContactValues) => {
|
|
59
|
+
setTouched((p) => ({ ...p, [key]: true }));
|
|
60
|
+
setErrors(validate(values));
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const onSubmit = (e: React.FormEvent) => {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
setSubmitted(true);
|
|
66
|
+
|
|
67
|
+
const eMap = validate(values);
|
|
68
|
+
setErrors(eMap);
|
|
69
|
+
|
|
70
|
+
if (Object.keys(eMap).length > 0) return;
|
|
71
|
+
|
|
72
|
+
console.log('Contact form submit:', values);
|
|
73
|
+
|
|
74
|
+
setValues({ firstName: '', lastName: '', email: '', phone: '', message: '' });
|
|
75
|
+
setTouched({});
|
|
76
|
+
setErrors({});
|
|
77
|
+
setSubmitted(false);
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const showError = (key: keyof ContactValues) => {
|
|
81
|
+
const shouldShow = submitted || touched[key];
|
|
82
|
+
return shouldShow ? errors[key] : undefined;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className="content content--background">
|
|
87
|
+
<div className="content__container content__container--small">
|
|
88
|
+
<div className="content__title__row">
|
|
89
|
+
<h2 className="content__title">Contact form</h2>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div className="contact">
|
|
93
|
+
<form className="contact__form" noValidate onSubmit={onSubmit}>
|
|
94
|
+
<label className="contact__form__group contact__form__group--2">
|
|
95
|
+
<span className="contact__form__label">Voornaam*</span>
|
|
96
|
+
<input
|
|
97
|
+
type="text"
|
|
98
|
+
className="contact__input"
|
|
99
|
+
placeholder="Enter your name"
|
|
100
|
+
aria-label="Enter your name"
|
|
101
|
+
value={values.firstName}
|
|
102
|
+
onChange={(e) => setField('firstName', e.target.value)}
|
|
103
|
+
onBlur={() => onBlur('firstName')}
|
|
104
|
+
aria-invalid={!!showError('firstName')}
|
|
105
|
+
aria-describedby="firstName-error"
|
|
106
|
+
/>
|
|
107
|
+
{showError('firstName') && (
|
|
108
|
+
<span className="contact__error" id="firstName-error">
|
|
109
|
+
{showError('firstName')}
|
|
110
|
+
</span>
|
|
111
|
+
)}
|
|
112
|
+
</label>
|
|
113
|
+
|
|
114
|
+
<label className="contact__form__group contact__form__group--2">
|
|
115
|
+
<span className="contact__form__label">Achternaam*</span>
|
|
116
|
+
<input
|
|
117
|
+
type="text"
|
|
118
|
+
className="contact__input"
|
|
119
|
+
placeholder="Enter your last name"
|
|
120
|
+
aria-label="Enter your last name"
|
|
121
|
+
value={values.lastName}
|
|
122
|
+
onChange={(e) => setField('lastName', e.target.value)}
|
|
123
|
+
onBlur={() => onBlur('lastName')}
|
|
124
|
+
aria-invalid={!!showError('lastName')}
|
|
125
|
+
aria-describedby="lastName-error"
|
|
126
|
+
/>
|
|
127
|
+
{showError('lastName') && (
|
|
128
|
+
<span className="contact__error" id="lastName-error">
|
|
129
|
+
{showError('lastName')}
|
|
130
|
+
</span>
|
|
131
|
+
)}
|
|
132
|
+
</label>
|
|
133
|
+
|
|
134
|
+
<label className="contact__form__group contact__form__group--2">
|
|
135
|
+
<span className="contact__form__label">Email*</span>
|
|
136
|
+
<input
|
|
137
|
+
type="email"
|
|
138
|
+
className="contact__input"
|
|
139
|
+
placeholder="Enter your email"
|
|
140
|
+
aria-label="Enter your email"
|
|
141
|
+
value={values.email}
|
|
142
|
+
onChange={(e) => setField('email', e.target.value)}
|
|
143
|
+
onBlur={() => onBlur('email')}
|
|
144
|
+
aria-invalid={!!showError('email')}
|
|
145
|
+
aria-describedby="email-error"
|
|
146
|
+
/>
|
|
147
|
+
{showError('email') && (
|
|
148
|
+
<span className="contact__error" id="email-error">
|
|
149
|
+
{showError('email')}
|
|
150
|
+
</span>
|
|
151
|
+
)}
|
|
152
|
+
</label>
|
|
153
|
+
|
|
154
|
+
<label className="contact__form__group contact__form__group--2">
|
|
155
|
+
<span className="contact__form__label">Telefoonnummer</span>
|
|
156
|
+
<input
|
|
157
|
+
type="tel"
|
|
158
|
+
className="contact__input"
|
|
159
|
+
placeholder="Enter your phone number"
|
|
160
|
+
aria-label="Enter your phone number"
|
|
161
|
+
value={values.phone}
|
|
162
|
+
onChange={(e) => setField('phone', e.target.value)}
|
|
163
|
+
onBlur={() => onBlur('phone')}
|
|
164
|
+
aria-invalid={!!showError('phone')}
|
|
165
|
+
aria-describedby="phone-error"
|
|
166
|
+
/>
|
|
167
|
+
{showError('phone') && (
|
|
168
|
+
<span className="contact__error" id="phone-error">
|
|
169
|
+
{showError('phone')}
|
|
170
|
+
</span>
|
|
171
|
+
)}
|
|
172
|
+
</label>
|
|
173
|
+
|
|
174
|
+
<label className="contact__form__group">
|
|
175
|
+
<span className="contact__form__label">Bericht*</span>
|
|
176
|
+
<textarea
|
|
177
|
+
className="contact__textarea"
|
|
178
|
+
placeholder="Enter your message"
|
|
179
|
+
aria-label="Enter your message"
|
|
180
|
+
value={values.message}
|
|
181
|
+
onChange={(e) => setField('message', e.target.value)}
|
|
182
|
+
onBlur={() => onBlur('message')}
|
|
183
|
+
aria-invalid={!!showError('message')}
|
|
184
|
+
aria-describedby="message-error"
|
|
185
|
+
/>
|
|
186
|
+
{showError('message') && (
|
|
187
|
+
<span className="contact__error" id="message-error">
|
|
188
|
+
{showError('message')}
|
|
189
|
+
</span>
|
|
190
|
+
)}
|
|
191
|
+
</label>
|
|
192
|
+
|
|
193
|
+
<div className="contact__form__actions">
|
|
194
|
+
<button type="submit" className="cta cta--primary">
|
|
195
|
+
Send message
|
|
196
|
+
</button>
|
|
197
|
+
</div>
|
|
198
|
+
{/*
|
|
199
|
+
{submitted && Object.keys(currentErrors).length > 0 && (
|
|
200
|
+
<div className="contact__error-summary" role="alert">
|
|
201
|
+
Controleer de velden in het formulier.
|
|
202
|
+
</div>
|
|
203
|
+
)} */}
|
|
204
|
+
</form>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
export default ContactForm;
|