ublo-lib 1.0.0

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 (168) hide show
  1. package/es/common/components/admin-links/admin-links.js +107 -0
  2. package/es/common/components/admin-links/admin-links.module.css +85 -0
  3. package/es/common/components/admin-links/index.js +2 -0
  4. package/es/common/components/analytics.js +46 -0
  5. package/es/common/components/breadcrumb.js +69 -0
  6. package/es/common/components/carousel-zone.js +61 -0
  7. package/es/common/components/carousel.js +365 -0
  8. package/es/common/components/cookie-consent/cookie-consent.js +111 -0
  9. package/es/common/components/cookie-consent/cookie-consent.module.css +103 -0
  10. package/es/common/components/cookie-consent/index.js +3 -0
  11. package/es/common/components/cookie-consent/messages.js +31 -0
  12. package/es/common/components/custom-contact-form/attachment.js +229 -0
  13. package/es/common/components/custom-contact-form/attachment.module.css +211 -0
  14. package/es/common/components/custom-contact-form/custom-contact-form.js +168 -0
  15. package/es/common/components/custom-contact-form/field.js +294 -0
  16. package/es/common/components/custom-contact-form/field.module.css +17 -0
  17. package/es/common/components/custom-contact-form/icons.js +55 -0
  18. package/es/common/components/custom-contact-form/index.js +2 -0
  19. package/es/common/components/custom-contact-form/index.module.css +119 -0
  20. package/es/common/components/custom-contact-form/messages.js +79 -0
  21. package/es/common/components/custom-contact-form/utils.js +132 -0
  22. package/es/common/components/date-picker/calendar.js +246 -0
  23. package/es/common/components/date-picker/calendar.module.css +123 -0
  24. package/es/common/components/date-picker/data.js +96 -0
  25. package/es/common/components/date-picker/date-item.js +127 -0
  26. package/es/common/components/date-picker/date-item.module.css +78 -0
  27. package/es/common/components/date-picker/date-picker.js +119 -0
  28. package/es/common/components/date-picker/date-picker.module.css +111 -0
  29. package/es/common/components/date-picker/helper.js +41 -0
  30. package/es/common/components/date-picker/helper.module.css +81 -0
  31. package/es/common/components/date-picker/index.js +3 -0
  32. package/es/common/components/date-picker/messages.js +34 -0
  33. package/es/common/components/date-picker/modes.js +42 -0
  34. package/es/common/components/date-picker/modes.module.css +90 -0
  35. package/es/common/components/date-picker/month.js +78 -0
  36. package/es/common/components/date-picker/month.module.css +54 -0
  37. package/es/common/components/date-picker/utils.js +121 -0
  38. package/es/common/components/error-404/error-404.js +51 -0
  39. package/es/common/components/error-404/error-404.module.css +55 -0
  40. package/es/common/components/error-404/index.js +2 -0
  41. package/es/common/components/error-404/messages.js +29 -0
  42. package/es/common/components/faq.js +54 -0
  43. package/es/common/components/info-buttons.js +116 -0
  44. package/es/common/components/plausible/hooks/use-plausible.js +33 -0
  45. package/es/common/components/plausible/index.js +7 -0
  46. package/es/common/components/plausible/plausible.js +20 -0
  47. package/es/common/components/plausible/services/callback.js +63 -0
  48. package/es/common/components/plausible/services/load.js +7 -0
  49. package/es/common/components/plausible/services/send-goal.js +10 -0
  50. package/es/common/components/popup.js +90 -0
  51. package/es/common/components/scroll-spy.js +53 -0
  52. package/es/common/components/tabbed-zones.js +110 -0
  53. package/es/common/components/unsupported-browser.js +158 -0
  54. package/es/common/components/video-player/controls.js +125 -0
  55. package/es/common/components/video-player/icons.js +45 -0
  56. package/es/common/components/video-player/index.js +1 -0
  57. package/es/common/components/video-player/player.module.css +151 -0
  58. package/es/common/components/video-player/utils.js +43 -0
  59. package/es/common/components/video-player/video-player.js +111 -0
  60. package/es/common/hooks/use-faq.js +44 -0
  61. package/es/common/hooks/use-in-view.js +73 -0
  62. package/es/common/hooks/use-injected-cms-markup.js +50 -0
  63. package/es/common/hooks/use-packages.js +127 -0
  64. package/es/common/hooks/use-scroll-direction.js +44 -0
  65. package/es/common/hooks/use-stay.js +32 -0
  66. package/es/common/hooks/use-sticky.js +38 -0
  67. package/es/common/hooks/use-tunnel.js +50 -0
  68. package/es/common/hooks/use-window-sizes.js +37 -0
  69. package/es/common/hooks/use-youtube-popup.js +62 -0
  70. package/es/common/hooks/use-zone-sync.js +65 -0
  71. package/es/common/utils/cookies.js +13 -0
  72. package/es/common/utils/copy.js +11 -0
  73. package/es/common/utils/dates.js +8 -0
  74. package/es/common/utils/events.js +25 -0
  75. package/es/common/utils/fetcher.js +37 -0
  76. package/es/common/utils/file-manager.js +14 -0
  77. package/es/common/utils/load-js.js +11 -0
  78. package/es/common/utils/msem-widget.js +16 -0
  79. package/es/common/utils/touch-device.js +1 -0
  80. package/es/common/utils/url-parameters.js +12 -0
  81. package/es/empty.d.ts +4 -0
  82. package/es/empty.d.ts.map +1 -0
  83. package/es/empty.js +6 -0
  84. package/es/esf/components/booking-form/data.js +213 -0
  85. package/es/esf/components/booking-form/field.js +140 -0
  86. package/es/esf/components/booking-form/hooks/use-custom-fields.js +20 -0
  87. package/es/esf/components/booking-form/hooks/use-stay.js +12 -0
  88. package/es/esf/components/booking-form/icons.js +50 -0
  89. package/es/esf/components/booking-form/index.js +78 -0
  90. package/es/esf/components/booking-form/lesson.js +59 -0
  91. package/es/esf/components/booking-form/lessons.js +93 -0
  92. package/es/esf/components/booking-form/messages.js +52 -0
  93. package/es/esf/components/booking-form/personal-data.js +73 -0
  94. package/es/esf/components/booking-form/progress-bar.js +35 -0
  95. package/es/esf/components/booking-form/response.js +42 -0
  96. package/es/esf/components/booking-form/steps.js +81 -0
  97. package/es/esf/components/booking-form/summary.js +138 -0
  98. package/es/esf/components/booking-form/utils.js +72 -0
  99. package/es/esf/components/contact-form/api.js +36 -0
  100. package/es/esf/components/contact-form/contact-form.js +293 -0
  101. package/es/esf/components/contact-form/contact-form.module.css +51 -0
  102. package/es/esf/components/contact-form/data.js +53 -0
  103. package/es/esf/components/contact-form/index.js +2 -0
  104. package/es/esf/components/contact-form/messages.js +75 -0
  105. package/es/esf/components/contact-form/validation.js +63 -0
  106. package/es/esf/components/covid-link/index.js +119 -0
  107. package/es/esf/components/covid-link/index.module.css +108 -0
  108. package/es/esf/components/covid-link/mask-icon.js +17 -0
  109. package/es/esf/components/covid-link/vax-pass-icon.js +34 -0
  110. package/es/esf/components/cp-form.js +65 -0
  111. package/es/esf/components/instructor-suggestions/fetcher.js +17 -0
  112. package/es/esf/components/instructor-suggestions/icons.js +266 -0
  113. package/es/esf/components/instructor-suggestions/index.js +181 -0
  114. package/es/esf/components/instructor-suggestions/loader.js +10 -0
  115. package/es/esf/components/instructor-suggestions/messages.js +16 -0
  116. package/es/esf/components/instructors-book/container.js +18 -0
  117. package/es/esf/components/instructors-book/details.js +120 -0
  118. package/es/esf/components/instructors-book/icons.js +266 -0
  119. package/es/esf/components/instructors-book/index.js +15 -0
  120. package/es/esf/components/instructors-book/link.js +17 -0
  121. package/es/esf/components/instructors-book/list-utils.js +21 -0
  122. package/es/esf/components/instructors-book/list.js +184 -0
  123. package/es/esf/components/instructors-book/loader.js +10 -0
  124. package/es/esf/components/instructors-book/messages.js +44 -0
  125. package/es/esf/components/instructors-book/utils.js +5 -0
  126. package/es/esf/components/levels.js +265 -0
  127. package/es/esf/components/loyal-customers/api.js +24 -0
  128. package/es/esf/components/loyal-customers/components/bin-icon.js +31 -0
  129. package/es/esf/components/loyal-customers/components/bin-icon.module.css +9 -0
  130. package/es/esf/components/loyal-customers/components/customer-form.js +105 -0
  131. package/es/esf/components/loyal-customers/components/customer-form.module.css +40 -0
  132. package/es/esf/components/loyal-customers/components/field.js +119 -0
  133. package/es/esf/components/loyal-customers/components/field.module.css +3 -0
  134. package/es/esf/components/loyal-customers/components/row.js +77 -0
  135. package/es/esf/components/loyal-customers/components/row.module.css +95 -0
  136. package/es/esf/components/loyal-customers/components/rows.js +38 -0
  137. package/es/esf/components/loyal-customers/components/rows.module.css +11 -0
  138. package/es/esf/components/loyal-customers/components/stay.js +37 -0
  139. package/es/esf/components/loyal-customers/components/stay.module.css +18 -0
  140. package/es/esf/components/loyal-customers/components/student-form.js +105 -0
  141. package/es/esf/components/loyal-customers/components/student-form.module.css +68 -0
  142. package/es/esf/components/loyal-customers/components/voucher.js +26 -0
  143. package/es/esf/components/loyal-customers/components/voucher.module.css +7 -0
  144. package/es/esf/components/loyal-customers/content.js +55 -0
  145. package/es/esf/components/loyal-customers/data.js +131 -0
  146. package/es/esf/components/loyal-customers/hooks/use-stored-rows.js +14 -0
  147. package/es/esf/components/loyal-customers/index.js +2 -0
  148. package/es/esf/components/loyal-customers/loyal-customers.js +141 -0
  149. package/es/esf/components/loyal-customers/loyal-customers.module.css +62 -0
  150. package/es/esf/components/loyal-customers/messages.js +59 -0
  151. package/es/esf/components/loyal-customers/utils.js +81 -0
  152. package/es/esf/components/village-maps/icons.js +35 -0
  153. package/es/esf/components/village-maps/index.js +214 -0
  154. package/es/esf/components/village-maps/messages.js +19 -0
  155. package/es/esf/components/village-maps/utils.js +26 -0
  156. package/es/esf/components/week-picker/index.js +244 -0
  157. package/es/esf/components/week-picker/messages.js +36 -0
  158. package/es/esf/components/week-picker/utils.js +65 -0
  159. package/es/esf/components/week-picker/week.js +52 -0
  160. package/es/esf/components/week-picker-2/index.js +283 -0
  161. package/es/esf/components/week-picker-2/messages.js +27 -0
  162. package/es/esf/components/week-picker-2/utils.js +65 -0
  163. package/es/esf/components/week-picker-2/week.js +55 -0
  164. package/es/esf/hooks/use-affiliation.js +26 -0
  165. package/es/esf/hooks/use-booking-links.js +36 -0
  166. package/es/esf/hooks/use-reviews.js +28 -0
  167. package/es/esf/hooks/use-season-products.js +100 -0
  168. package/package.json +51 -0
@@ -0,0 +1,294 @@
1
+ import * as React from "react";
2
+ import classnames from "classnames";
3
+ import { useUbloContext } from "ublo/with-ublo";
4
+ import Attachment from "./attachment";
5
+ import Input from "dt-design-system/es/input";
6
+ import Textarea from "dt-design-system/es/textarea";
7
+ import Select from "dt-design-system/es/select";
8
+ import Checkbox from "dt-design-system/es/checkbox";
9
+ import NumberPicker from "dt-design-system/es/number-picker";
10
+ import * as Fetcher from "../../utils/fetcher";
11
+ import { FIELD_TESTS_ERROR_CODES, validate } from "./utils";
12
+ import * as Messages from "./messages";
13
+ import styles from "./field.module.css";
14
+ import { jsx as _jsx } from "react/jsx-runtime";
15
+ import { jsxs as _jsxs } from "react/jsx-runtime";
16
+
17
+ const getTag = type => {
18
+ switch (true) {
19
+ case type === "textarea":
20
+ return Textarea;
21
+
22
+ case type === "select":
23
+ return Select;
24
+
25
+ case type === "checkbox":
26
+ return Checkbox;
27
+
28
+ case type === "number":
29
+ return NumberPicker;
30
+
31
+ default:
32
+ return Input;
33
+ }
34
+ };
35
+
36
+ const getProps = (type, name, className, data, required, placeholder, autoSizing, onChange) => {
37
+ const commonProps = {
38
+ className,
39
+ value: data[name].value,
40
+ placeholder,
41
+ required,
42
+ autoSizing
43
+ };
44
+
45
+ if (type === "textarea" || type === "select") {
46
+ return {
47
+ onValueChange: onChange,
48
+ ...commonProps
49
+ };
50
+ }
51
+
52
+ if (type === "number") {
53
+ return {
54
+ withInput: true,
55
+ onChange,
56
+ ...commonProps
57
+ };
58
+ }
59
+
60
+ return {
61
+ type,
62
+ onValueChange: onChange,
63
+ ...commonProps
64
+ };
65
+ };
66
+
67
+ const Checkboxes = ({
68
+ checkboxes,
69
+ data,
70
+ name,
71
+ onChange,
72
+ disabled
73
+ }) => {
74
+ if (!checkboxes.length) return null;
75
+ return checkboxes.map((checkbox, i) => {
76
+ const {
77
+ value = checkbox,
78
+ label = checkbox
79
+ } = checkbox;
80
+ const checked = data[name].value.includes(value);
81
+ return _jsx(Checkbox, {
82
+ label: label,
83
+ onValueChange: onChange,
84
+ value: checked,
85
+ disabled: disabled
86
+ }, i);
87
+ });
88
+ };
89
+
90
+ const getLabel = (lang, label, required) => {
91
+ if (!required) return `${label} (${Messages.get(lang, "optionnal")})`;
92
+ return label;
93
+ };
94
+
95
+ const Inner = ({
96
+ site,
97
+ lang,
98
+ label,
99
+ kind,
100
+ name,
101
+ classes,
102
+ field,
103
+ data,
104
+ setData,
105
+ disabled,
106
+ activated,
107
+ setActivated,
108
+ valid,
109
+ setValid
110
+ }) => {
111
+ const {
112
+ type,
113
+ className,
114
+ options: fieldOptions = [],
115
+ placeholder,
116
+ required,
117
+ autoSizing
118
+ } = field;
119
+ const [options, setOptions] = React.useState(fieldOptions);
120
+ const fieldClasses = classnames({
121
+ [className]: className,
122
+ [classes]: classes
123
+ });
124
+ const fieldsetRef = React.useRef();
125
+ const Tag = getTag(type);
126
+ const onChange = React.useCallback((value, event) => {
127
+ const isAutoFilled = document.activeElement !== event?.target;
128
+
129
+ if (fieldsetRef.current) {
130
+ const values = Array.from(fieldsetRef.current.querySelectorAll("input")).reduce((acc, checkbox) => {
131
+ const parent = checkbox.closest("label");
132
+ const value = parent.textContent.trim();
133
+ const {
134
+ checked
135
+ } = checkbox;
136
+ return checked ? [...acc, value] : acc;
137
+ }, []);
138
+ setData(data => ({ ...data,
139
+ [name]: {
140
+ value: values,
141
+ valid: true
142
+ }
143
+ }));
144
+ setValid(true);
145
+ return;
146
+ }
147
+
148
+ if (type === "number" || isAutoFilled) {
149
+ const isValid = validate(type, value, required);
150
+ setData(data => ({ ...data,
151
+ [name]: { ...data[name],
152
+ value,
153
+ valid: isValid
154
+ }
155
+ }));
156
+ setValid(isValid);
157
+ setActivated(true);
158
+ return;
159
+ }
160
+
161
+ setData(data => ({ ...data,
162
+ [name]: { ...data[name],
163
+ value
164
+ }
165
+ }));
166
+ }, [name, required, setActivated, setData, setValid, type]);
167
+ const props = getProps(type, name, fieldClasses, data, required, placeholder, autoSizing, onChange);
168
+
169
+ const onBlur = e => {
170
+ const {
171
+ value
172
+ } = e.target;
173
+ const isValid = validate(type, value, required);
174
+ setData(data => ({ ...data,
175
+ [name]: { ...data[name],
176
+ valid: isValid
177
+ }
178
+ }));
179
+ setValid(isValid);
180
+ setActivated(true);
181
+ };
182
+
183
+ React.useEffect(() => {
184
+ if (name === "subject") {
185
+ const getSubjects = async () => {
186
+ const body = kind ? {
187
+ kind
188
+ } : {};
189
+ const subjects = await Fetcher.post(`https://contacts.valraiso.net/api/subjects/${site}`, body);
190
+
191
+ if (subjects?.length === 1) {
192
+ const value = subjects?.[0]?.value || "";
193
+ onChange(value);
194
+ }
195
+
196
+ setOptions(subjects);
197
+ };
198
+
199
+ getSubjects();
200
+ }
201
+ }, [kind, name, onChange, site]);
202
+
203
+ if (type === "checkbox-group") {
204
+ return _jsxs("fieldset", {
205
+ ref: fieldsetRef,
206
+ children: [label && _jsx("legend", {
207
+ children: label
208
+ }), _jsx(Checkboxes, {
209
+ checkboxes: options,
210
+ data: data,
211
+ name: name,
212
+ onChange: onChange,
213
+ disabled: disabled
214
+ })]
215
+ });
216
+ }
217
+
218
+ const fieldLabel = getLabel(lang, label, required);
219
+ const isInputValid = required && activated && valid;
220
+ const errorMessage = required && activated && !valid && Messages.get(lang, FIELD_TESTS_ERROR_CODES[type]);
221
+ return _jsx(Tag, {
222
+ label: fieldLabel,
223
+ options: options,
224
+ disabled: disabled,
225
+ valid: isInputValid,
226
+ error: errorMessage,
227
+ onBlur: onBlur,
228
+ ...props
229
+ });
230
+ };
231
+
232
+ const Field = ({
233
+ kind,
234
+ name,
235
+ field,
236
+ data,
237
+ setData,
238
+ settings,
239
+ presets
240
+ }) => {
241
+ const {
242
+ lang,
243
+ config
244
+ } = useUbloContext();
245
+ const {
246
+ type,
247
+ label,
248
+ hidden,
249
+ fullWidth,
250
+ required
251
+ } = field;
252
+ const [activated, setActivated] = React.useState(false);
253
+ const [valid, setValid] = React.useState(!required || false);
254
+ const disabled = presets && Object.keys(presets).includes(name);
255
+ const isTitle = type === "title";
256
+ const classes = classnames({
257
+ [styles.fieldFullWidth]: isTitle || fullWidth,
258
+ [styles.fieldHidden]: hidden
259
+ });
260
+ if (isTitle) return _jsx("div", {
261
+ className: styles.title,
262
+ children: label
263
+ });
264
+
265
+ if (type === "attachment") {
266
+ return _jsx(Attachment, {
267
+ lang: lang,
268
+ name: name,
269
+ label: label,
270
+ data: data,
271
+ setData: setData,
272
+ settings: settings
273
+ });
274
+ }
275
+
276
+ return _jsx(Inner, {
277
+ site: config.site,
278
+ kind: kind,
279
+ label: label,
280
+ lang: lang,
281
+ name: name,
282
+ classes: classes,
283
+ field: field,
284
+ data: data,
285
+ setData: setData,
286
+ disabled: disabled,
287
+ activated: activated,
288
+ setActivated: setActivated,
289
+ valid: valid,
290
+ setValid: setValid
291
+ });
292
+ };
293
+
294
+ export default Field;
@@ -0,0 +1,17 @@
1
+ .fieldHidden {
2
+ display: none !important;
3
+ }
4
+
5
+ @media (min-width: 780px) {
6
+ .fieldFullWidth,
7
+ .title {
8
+ grid-column: 1 / -1;
9
+ }
10
+ }
11
+
12
+ .title {
13
+ color: var(--ds-grey-700, #171e30);
14
+ font-size: 14px;
15
+ text-transform: uppercase;
16
+ font-weight: 700;
17
+ }
@@ -0,0 +1,55 @@
1
+ import * as React from "react";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { jsxs as _jsxs } from "react/jsx-runtime";
4
+
5
+ const Icon = ({
6
+ children,
7
+ ...props
8
+ }) => _jsx("svg", {
9
+ viewBox: "0 0 24 24",
10
+ ...props,
11
+ children: children
12
+ });
13
+
14
+ export const Upload = props => {
15
+ return _jsxs(Icon, { ...props,
16
+ children: [_jsx("path", {
17
+ d: "M11.3 11.3a1 1 0 0 1 1.4 0l4 4a1 1 0 0 1-1.4 1.4L12 13.42l-3.3 3.3a1 1 0 0 1-1.4-1.42l4-4Z"
18
+ }), _jsx("path", {
19
+ d: "M12 11a1 1 0 0 1 1 1v9a1 1 0 1 1-2 0v-9a1 1 0 0 1 1-1Z"
20
+ }), _jsx("path", {
21
+ d: "M8.66 2a9 9 0 0 1 8.82 6H18a6 6 0 0 1 2.87 11.27 1 1 0 0 1-.96-1.76A4 4 0 0 0 18 10h-1.26a1 1 0 0 1-.97-.75 7 7 0 1 0-12.02 6.39 1 1 0 1 1-1.5 1.32A9 9 0 0 1 8.66 2.01Z"
22
+ }), _jsx("path", {
23
+ d: "M11.3 11.3a1 1 0 0 1 1.4 0l4 4a1 1 0 0 1-1.4 1.4L12 13.42l-3.3 3.3a1 1 0 0 1-1.4-1.42l4-4Z"
24
+ })]
25
+ });
26
+ };
27
+ export const File = props => {
28
+ return _jsxs(Icon, { ...props,
29
+ children: [_jsx("path", {
30
+ d: "M3.88 1.88A3 3 0 0 1 6 1h7a1 1 0 0 1 .7.3l7 7a1 1 0 0 1 .3.7v11a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3V4a3 3 0 0 1 .88-2.12ZM6 3a1 1 0 0 0-1 1v16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V9.41L12.59 3H6Z"
31
+ }), _jsx("path", {
32
+ d: "M13 1a1 1 0 0 1 1 1v6h6a1 1 0 1 1 0 2h-7a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1Z"
33
+ })]
34
+ });
35
+ };
36
+ export const Bin = props => {
37
+ return _jsxs(Icon, { ...props,
38
+ children: [_jsx("path", {
39
+ d: "M2 6a1 1 0 011-1h18a1 1 0 110 2H3a1 1 0 01-1-1z"
40
+ }), _jsx("path", {
41
+ fillRule: "evenodd",
42
+ clipRule: "evenodd",
43
+ d: "M10 3a1 1 0 00-1 1v1h6V4a1 1 0 00-1-1h-4zm7 2V4a3 3 0 00-3-3h-4a3 3 0 00-3 3v1H5a1 1 0 00-1 1v14a3 3 0 003 3h10a3 3 0 003-3V6a1 1 0 00-1-1h-2zM6 7v13a1 1 0 001 1h10a1 1 0 001-1V7H6z"
44
+ }), _jsx("path", {
45
+ d: "M10 10a1 1 0 011 1v6a1 1 0 11-2 0v-6a1 1 0 011-1zM14 10a1 1 0 011 1v6a1 1 0 11-2 0v-6a1 1 0 011-1z"
46
+ })]
47
+ });
48
+ };
49
+ export const Spinner = props => {
50
+ return _jsx(Icon, { ...props,
51
+ children: _jsx("path", {
52
+ d: "M10.7 3V1.4a1.3 1.3 0 012.6 0V3c0 .7-.6 1.3-1.3 1.3-.7 0-1.3-.6-1.3-1.3zM10.7 22.7V21a1.3 1.3 0 012.6 0v1.8c0 .7-.6 1.3-1.3 1.3-.7 0-1.3-.6-1.3-1.3zM17.4 6.6c-.5-.5-.5-1.3 0-1.8l1.3-1.3a1.3 1.3 0 111.8 1.8l-1.3 1.3a1.3 1.3 0 01-1.8 0zM3.5 20.5c-.5-.5-.5-1.3 0-1.8l1.3-1.3a1.3 1.3 0 011.8 1.8l-1.3 1.3a1.3 1.3 0 01-1.8 0zM21 13.3a1.3 1.3 0 110-2.6h1.7c.7 0 1.3.6 1.3 1.3 0 .7-.6 1.3-1.3 1.3H21zM1.3 13.3a1.3 1.3 0 110-2.6H3a1.3 1.3 0 010 2.6H1.3zM18.7 20.5l-1.3-1.3a1.3 1.3 0 011.8-1.8l1.3 1.3a1.3 1.3 0 01-1 2.2l-.8-.4zM4.8 6.6L3.5 5.3a1.3 1.3 0 011.8-1.8l1.3 1.3a1.3 1.3 0 01-1.8 1.8z"
53
+ })
54
+ });
55
+ };
@@ -0,0 +1,2 @@
1
+ import CustomContactForm from "./custom-contact-form";
2
+ export default CustomContactForm;
@@ -0,0 +1,119 @@
1
+ .root {
2
+ width: 100%;
3
+ display: flex;
4
+ flex-direction: column;
5
+ margin: 0 auto;
6
+ padding-bottom: 100px;
7
+ }
8
+
9
+ .title {
10
+ color: var(--ds-grey-700, #171e30);
11
+ font-size: 18px;
12
+ line-height: 1.2em;
13
+ text-transform: uppercase;
14
+ font-weight: 700;
15
+ }
16
+
17
+ @media (min-width: 480px) {
18
+ .title {
19
+ font-size: 25px;
20
+ }
21
+ }
22
+
23
+ @media (min-width: 1001px) {
24
+ .title {
25
+ font-size: 34px;
26
+ }
27
+ }
28
+
29
+ .inner {
30
+ display: grid;
31
+ grid-template-columns: 1fr;
32
+ gap: 34px;
33
+ }
34
+
35
+ @media (min-width: 780px) {
36
+ .inner {
37
+ grid-template-columns: repeat(2, 1fr);
38
+ }
39
+ }
40
+
41
+ .bottom {
42
+ display: flex;
43
+ align-items: center;
44
+ justify-content: flex-end;
45
+ margin-top: 20px;
46
+ }
47
+
48
+ .message {
49
+ font-size: 14px;
50
+ color: var(--ds-grey-700, #171e30);
51
+ }
52
+
53
+ @media (min-width: 480px) {
54
+ .message {
55
+ font-size: 17px;
56
+ }
57
+ }
58
+
59
+ @media (min-width: 1001px) {
60
+ .message {
61
+ font-size: 20px;
62
+ }
63
+ }
64
+
65
+ .resetButton {
66
+ margin-right: 10px;
67
+ }
68
+
69
+ .submitButton {
70
+ margin-left: auto;
71
+ }
72
+
73
+ .sendingOverlay {
74
+ position: fixed;
75
+ top: 0;
76
+ left: 0;
77
+ width: 100vw;
78
+ height: 100vh;
79
+ display: flex;
80
+ flex-direction: column;
81
+ align-items: center;
82
+ justify-content: center;
83
+ background-color: rgba(255, 255, 255, 0.5);
84
+ backdrop-filter: blur(6px);
85
+ }
86
+
87
+ .sendingIcon {
88
+ width: 30px;
89
+ height: 30px;
90
+ margin-bottom: 20px;
91
+ fill: var(--ds-grey-700, #171e30);
92
+ animation: icon-loading-spin 1280ms
93
+ var(--ds-transition-easing, cubic-bezier(0.4, 0, 0.2, 1)) infinite;
94
+ }
95
+
96
+ @keyframes icon-loading-spin {
97
+ 100% {
98
+ transform: rotate(360deg);
99
+ }
100
+ }
101
+
102
+ .sendingMessage {
103
+ max-width: 420px;
104
+ color: var(--ds-grey-700, #171e30);
105
+ font-size: 12px;
106
+ text-align: center;
107
+ }
108
+
109
+ @media (min-width: 480px) {
110
+ .sendingMessage {
111
+ font-size: 14px;
112
+ }
113
+ }
114
+
115
+ @media (min-width: 1001px) {
116
+ .sendingMessage {
117
+ font-size: 17px;
118
+ }
119
+ }
@@ -0,0 +1,79 @@
1
+ const locales = {
2
+ fr: {
3
+ send: "Envoyer",
4
+ reset: "Réinitialiser le formulaire",
5
+ choose: "Selectionner",
6
+ sentTitle: "Message envoyé !",
7
+ sent: "Votre message a bien été pris en compte.",
8
+ errorTitle: "Erreur",
9
+ error: "Une erreur est survenue, veuillez réessayer.",
10
+ "Internal Server Error": "Une erreur est survenue, veuillez réessayer.",
11
+ settingsError: "Erreur lors du chargement de la configuration du formulaire",
12
+ sending: "Envoi en cours, cette opération peut prendre du temps en fonctionne de la qualité du réseau et de la taille de vos pièces jointes",
13
+ attachmentPlaceholder: "Cliquez sur cet encart ou bien glissez/déposez vos fichiers dedans pour les mettre en ligne.",
14
+ attachmentHelper1: "fichiers maximum",
15
+ attachmentHelper2: "MO par fichier maximum",
16
+ attachmentHelper3: "Formats autorisés :",
17
+ attachmentOverlay: "Déposez vos fichiers ici",
18
+ attachmentFormatError: "Ce format de fichier n'est pas autorisé",
19
+ attachmentNumberError: "Vous avez choisi un trop grand nombre de fichiers",
20
+ attachmentSizeError: "Vous avez choisi un fichier trop lourd",
21
+ MISSING_RECIPIENT: "Aucun sujet selectionné, veuillez réessayer en choisissant un sujet.",
22
+ MISSING_EMAIL: "Aucun email renseigné, veuillez réessayer en renseignant votre adresse email.",
23
+ NO_RECIPIENT_FOUND: "Erreur lors de la récupération du destinataire final.",
24
+ NO_SETTINGS_FOUND: "Erreur lors de la récupération du paramétrage du formulaire.",
25
+ ATTACHMENT_NOT_ALLOWED: "Les pièces jointes ne sont pas autorisées dans ce formulaire.",
26
+ ATTACHMENT_NUMBER_TO_HIGH: "Nombre de pièces jointes trop important.",
27
+ ATTACHMENT_TRUNCATED: "Pièce(s) jointe(s) corrompue(s), veuillez réessayer.",
28
+ ATTACHMENT_TO_BIG: "Pièce(s) jointe(s) trop lourdes, veuillez réessayer avec d'autres fichiers.",
29
+ WRONG_ATTACHMENT_FORMAT: "Pièce(s) jointe(s) au format incompatible.",
30
+ TESTS_TEXT_ERROR: "Ce champ doit faire au moins 2 caractères",
31
+ TESTS_EMAIL_ERROR: 'L\'email doit être au format "john.doe@domaine.com"',
32
+ TESTS_DATE_ERROR: 'La date doit être format "AAAA-MM-JJ"',
33
+ TESTS_PHONE_ERROR: 'Le téléphone doit être au format "0612345678" ou "+33612345678"',
34
+ TESTS_TEXTAREA_ERROR: "Ce champ ne doit pas être vide",
35
+ TESTS_SELECT_ERROR: "Ce champ ne doit pas être vide",
36
+ optionnal: "optionnel"
37
+ },
38
+ en: {
39
+ send: "Send",
40
+ choose: "Choose an option",
41
+ sentTitle: "Message sent!",
42
+ sent: "Your message has been sent.",
43
+ errorTitle: "Error",
44
+ error: "An error occured, please try again.",
45
+ "Internal Server Error": "An error occured, please try again.",
46
+ settingsError: "An error occured while loading the form configuration",
47
+ sending: "Sending... This operation can take a while depending on your network quality and the size of the attachment",
48
+ attachmentPlaceholder: "Clic on this area or drag'n'drop your files inside in order to upload them.",
49
+ attachmentHelper1: "files maximum",
50
+ attachmentHelper2: "MB maximum for each file",
51
+ attachmentHelper3: "Authorized formats:",
52
+ attachmentOverlay: "Drop your files here",
53
+ attachmentFormatError: "This file format is not authorized",
54
+ attachmentNumberError: "You have chosen to much files",
55
+ attachmentSizeError: "You have chosen a file to big",
56
+ MISSING_RECIPIENT: "No subject selected, please try again after selecting a subject.",
57
+ MISSING_EMAIL: "No email provied, please try again after filling the email field.",
58
+ NO_RECIPIENT_FOUND: "Error while retrieving the final recipient.",
59
+ NO_SETTINGS_FOUND: "Error while getting the form settings.",
60
+ ATTACHMENT_NOT_ALLOWED: "Attachments are not allowed in this form.",
61
+ ATTACHMENT_NUMBER_TO_HIGH: "Attachment number to high.",
62
+ ATTACHMENT_TRUNCATED: "Corrupted attachment(s), please try again with other files.",
63
+ ATTACHMENT_TO_BIG: "Attachment(s) to big, please try again with other files.",
64
+ WRONG_ATTACHMENT_FORMAT: "Attachment(s) in the wrong format, please try again with other files.",
65
+ TESTS_TEXT_ERROR: "This field must be at least 2 characters long",
66
+ TESTS_EMAIL_ERROR: 'The email must be in the format "john.doe@domain.com"',
67
+ TESTS_DATE_ERROR: 'The date must be in the following format: "YYYY-MM-DD"',
68
+ TESTS_PHONE_ERROR: 'The date must be in the following formats: "0612345678" or "+33612345678"',
69
+ TESTS_TEXTAREA_ERROR: "this field cannot be empty",
70
+ TESTS_SELECT_ERROR: "this field cannot be empty",
71
+ optionnal: "optionnal"
72
+ }
73
+ };
74
+ export const get = (lang, id) => {
75
+ const _lang = lang === "fr" ? "fr" : "en";
76
+
77
+ if (!locales || !locales[_lang]) return id;
78
+ return locales[_lang][id] || locales.fr[id] || `??${id}??`;
79
+ };
@@ -0,0 +1,132 @@
1
+ import * as Fetcher from "../../utils/fetcher";
2
+ export const TESTS = {
3
+ text: /^[^%]{2,}$/,
4
+ date: /([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))/,
5
+ email: /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@(([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{2,})$/i,
6
+ phone: /^\+?[0-9]{3}-?[0-9]{6,12}$/,
7
+ textarea: /^(?!\s*$).+/,
8
+ select: /^(?!\s*$).+/
9
+ };
10
+ export const FIELD_TESTS_ERROR_CODES = {
11
+ text: "TESTS_TEXT_ERROR",
12
+ date: "TESTS_DATE_ERROR",
13
+ email: "TESTS_EMAIL_ERROR",
14
+ phone: "TESTS_PHONE_ERROR",
15
+ textarea: "TESTS_TEXTAREA_ERROR",
16
+ select: "TESTS_SELECT_ERROR"
17
+ };
18
+ const IGNORED_FIELDS = ["title", "subject", "attachment", "checkbox", "checkbox-group"];
19
+ export const getInitialFormState = (fields, presets) => Object.keys(fields).reduce((acc, key) => {
20
+ const field = fields[key];
21
+ const {
22
+ required,
23
+ type
24
+ } = field;
25
+ if (type === "title") return acc;
26
+ if (key === "subject") return { ...acc,
27
+ [key]: {
28
+ value: "",
29
+ valid: true
30
+ }
31
+ };
32
+
33
+ if (type === "checkbox") {
34
+ const value = presets?.[key] ?? false;
35
+ return { ...acc,
36
+ [key]: {
37
+ value,
38
+ valid: validate(type, value, required)
39
+ }
40
+ };
41
+ }
42
+
43
+ if (type === "number") {
44
+ const value = presets?.[key] ?? 0;
45
+ return { ...acc,
46
+ [key]: {
47
+ value,
48
+ valid: validate(type, value, required)
49
+ }
50
+ };
51
+ }
52
+
53
+ if (type === "checkbox-group") {
54
+ const value = presets?.[key].length || [];
55
+ return { ...acc,
56
+ [key]: {
57
+ value,
58
+ valid: validate(type, value, required)
59
+ }
60
+ };
61
+ }
62
+
63
+ const value = presets?.[key] ?? "";
64
+ return { ...acc,
65
+ [key]: {
66
+ value,
67
+ valid: validate(type, value, required)
68
+ }
69
+ };
70
+ }, {});
71
+ export const validate = (type, value, required) => {
72
+ if (IGNORED_FIELDS.includes(type) || !required) return true;
73
+ if (type === "number") return value >= 0;
74
+ if (!value) return false;
75
+ const regex = TESTS[type];
76
+ if (!regex) return false;
77
+ return regex.test(value);
78
+ };
79
+ export const convertToFileList = list => {
80
+ const dataTransfer = new DataTransfer();
81
+ list.forEach(item => dataTransfer.items.add(item));
82
+ return dataTransfer.files;
83
+ };
84
+ export const send = async ({
85
+ site,
86
+ data,
87
+ fields,
88
+ subjectPrefix,
89
+ subjectSuffix
90
+ }) => {
91
+ const endpoint = `https://contacts.valraiso.net/api/contact/${site}`;
92
+ const dataKeys = Object.keys(data);
93
+ const formData = new FormData();
94
+ dataKeys.forEach(key => {
95
+ if (key === "attachment") {
96
+ return Array.from(data[key].value).forEach(file => {
97
+ formData.append(key, file);
98
+ });
99
+ } else {
100
+ const {
101
+ formatter
102
+ } = fields[key];
103
+ const formatedValue = typeof formatter === "function" ? formatter(data[key].value) : data[key].value;
104
+ formData.append(key, formatedValue);
105
+ }
106
+ });
107
+
108
+ if (subjectPrefix) {
109
+ formData.append("subjectPrefix", subjectPrefix);
110
+ }
111
+
112
+ if (subjectSuffix) {
113
+ formData.append("subjectSuffix", subjectSuffix);
114
+ }
115
+
116
+ const response = await fetch(endpoint, {
117
+ method: "POST",
118
+ body: formData
119
+ });
120
+ const json = response.json();
121
+ return json;
122
+ };
123
+ export const getSettings = async site => {
124
+ const endpoint = `https://contacts.valraiso.net/api/settings/${site}`;
125
+
126
+ try {
127
+ const response = await Fetcher.get(endpoint);
128
+ return response;
129
+ } catch (e) {
130
+ return undefined;
131
+ }
132
+ };