bukazu-portal-react 2.1.21 → 3.0.2

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 (191) hide show
  1. package/.github/workflows/dependabot.yml +11 -0
  2. package/.github/workflows/node.js.yml +31 -0
  3. package/.prettierrc +3 -6
  4. package/CHANGELOG.MD +5 -0
  5. package/babel.config.json +1 -1
  6. package/build/index.css +1 -2312
  7. package/build/portal.es.js +35483 -0
  8. package/build/portal.umd.js +596 -0
  9. package/{build/calendar.html → calendar.html} +2 -4
  10. package/coverage/clover.xml +28 -0
  11. package/coverage/coverage-final.json +2 -0
  12. package/coverage/lcov-report/base.css +224 -0
  13. package/coverage/lcov-report/block-navigation.js +87 -0
  14. package/coverage/lcov-report/favicon.png +0 -0
  15. package/coverage/lcov-report/helper.ts.html +142 -0
  16. package/coverage/lcov-report/index.html +116 -0
  17. package/coverage/lcov-report/prettify.css +1 -0
  18. package/coverage/lcov-report/prettify.js +2 -0
  19. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  20. package/coverage/lcov-report/sorter.js +196 -0
  21. package/coverage/lcov.info +36 -0
  22. package/cypress/{integration → e2e}/.examples/actions.spec.js +0 -0
  23. package/cypress/{integration → e2e}/.examples/aliasing.spec.js +0 -0
  24. package/cypress/{integration → e2e}/.examples/assertions.spec.js +0 -0
  25. package/cypress/{integration → e2e}/.examples/connectors.spec.js +0 -0
  26. package/cypress/{integration → e2e}/.examples/cookies.spec.js +0 -0
  27. package/cypress/{integration → e2e}/.examples/cypress_api.spec.js +0 -0
  28. package/cypress/{integration → e2e}/.examples/files.spec.js +0 -0
  29. package/cypress/{integration → e2e}/.examples/local_storage.spec.js +0 -0
  30. package/cypress/{integration → e2e}/.examples/location.spec.js +0 -0
  31. package/cypress/{integration → e2e}/.examples/misc.spec.js +0 -0
  32. package/cypress/{integration → e2e}/.examples/navigation.spec.js +0 -0
  33. package/cypress/{integration → e2e}/.examples/network_requests.spec.js +0 -0
  34. package/cypress/{integration → e2e}/.examples/querying.spec.js +0 -0
  35. package/cypress/{integration → e2e}/.examples/spies_stubs_clocks.spec.js +0 -0
  36. package/cypress/{integration → e2e}/.examples/traversal.spec.js +0 -0
  37. package/cypress/{integration → e2e}/.examples/utilities.spec.js +0 -0
  38. package/cypress/{integration → e2e}/.examples/viewport.spec.js +0 -0
  39. package/cypress/{integration → e2e}/.examples/waiting.spec.js +0 -0
  40. package/cypress/{integration → e2e}/.examples/window.spec.js +0 -0
  41. package/cypress/{integration → e2e}/booking.spec.js +0 -0
  42. package/cypress/{integration → e2e}/calendar.spec.js +0 -0
  43. package/cypress/{integration → e2e}/search.spec.js +0 -0
  44. package/cypress/support/commands.ts +37 -0
  45. package/cypress/support/component-index.html +12 -0
  46. package/cypress/support/component.ts +39 -0
  47. package/cypress/support/{index.js → e2e.js} +0 -0
  48. package/cypress.config.ts +15 -0
  49. package/{dev.js → dev.tsx} +1 -1
  50. package/index.html +15 -0
  51. package/{build/invalid-calendar.html → invalid-calendar.html} +0 -0
  52. package/jest.config.js +195 -0
  53. package/package.json +35 -40
  54. package/reviews.html +16 -0
  55. package/src/_lib/{SearchQueries.js → SearchQueries.ts} +8 -2
  56. package/src/_lib/{countries.js → countries.ts} +0 -0
  57. package/src/_lib/date_helper.ts +27 -0
  58. package/src/_lib/{queries.js → queries.ts} +24 -5
  59. package/src/components/App.tsx +132 -0
  60. package/src/components/AppContext.ts +14 -0
  61. package/src/components/CalendarPage/BookingForm.tsx +42 -0
  62. package/src/components/CalendarPage/Calendar.tsx +50 -0
  63. package/src/components/CalendarPage/CalendarPage.tsx +43 -0
  64. package/src/components/CalendarPage/CalendarParts/CalendarContext.tsx +89 -0
  65. package/src/components/CalendarPage/CalendarParts/CalendarHeader.tsx +72 -0
  66. package/src/components/CalendarPage/CalendarParts/DayClasses.ts +111 -0
  67. package/src/components/CalendarPage/CalendarParts/GenerateCalendar.tsx +64 -0
  68. package/src/components/CalendarPage/CalendarParts/Legend.tsx +33 -0
  69. package/src/components/CalendarPage/CalendarParts/MonthHeader.tsx +15 -0
  70. package/src/components/CalendarPage/CalendarParts/Months.tsx +37 -0
  71. package/src/components/CalendarPage/CalendarParts/RenderCells.tsx +94 -0
  72. package/src/components/CalendarPage/CalendarParts/SingleMonth.tsx +72 -0
  73. package/src/components/CalendarPage/CalendarParts/StartBooking.tsx +17 -0
  74. package/src/components/CalendarPage/CalendarParts/WeekDays.tsx +27 -0
  75. package/src/components/CalendarPage/FormCreator.tsx +213 -0
  76. package/src/components/CalendarPage/FormItems/{Date.js → Date.tsx} +10 -2
  77. package/src/components/CalendarPage/FormItems/{NumberSelect.js → NumberSelect.tsx} +1 -1
  78. package/src/components/CalendarPage/FormItems/{Select.js → Select.tsx} +0 -0
  79. package/src/components/CalendarPage/FormItems/{index.js → index.ts} +0 -0
  80. package/src/components/CalendarPage/PriceField/Price.tsx +58 -0
  81. package/src/components/CalendarPage/PriceField/Queries.ts +23 -0
  82. package/src/components/CalendarPage/PriceField/index.tsx +127 -0
  83. package/src/components/CalendarPage/Summary/{CostRow.js → CostRow.tsx} +19 -3
  84. package/src/components/CalendarPage/Summary/{CostSection.js → CostSection.tsx} +5 -1
  85. package/src/components/CalendarPage/Summary/{CostSummary.js → CostSummary.tsx} +19 -10
  86. package/src/components/CalendarPage/Summary/Description.tsx +27 -0
  87. package/src/components/CalendarPage/Summary/{InsurancesAndRequired.js → InsurancesAndRequired.tsx} +21 -2
  88. package/src/components/CalendarPage/Summary/Object.tsx +59 -0
  89. package/src/components/CalendarPage/Summary/{OnSite.js → OnSite.tsx} +9 -9
  90. package/src/components/CalendarPage/Summary/{OptionalNotOnSite.js → OptionalNotOnSite.tsx} +19 -18
  91. package/src/components/CalendarPage/Summary/{OptionalOnSite.js → OptionalOnSite.tsx} +6 -1
  92. package/src/components/CalendarPage/Summary/{Queries.js → Queries.ts} +3 -3
  93. package/src/components/CalendarPage/Summary/RentAndDiscount.tsx +30 -0
  94. package/src/components/CalendarPage/Summary/{Totals.js → Totals.tsx} +8 -3
  95. package/src/components/CalendarPage/Summary/cost_types.d.ts +31 -0
  96. package/src/components/CalendarPage/Summary/index.tsx +24 -0
  97. package/src/components/CalendarPage/calender_types.d.ts +16 -0
  98. package/src/components/CalendarPage/formParts/AssistanceMessage.tsx +60 -0
  99. package/src/components/CalendarPage/formParts/{BookingHelpers.js → BookingHelpers.tsx} +3 -3
  100. package/src/components/CalendarPage/formParts/{BookingOrOption.js → BookingOrOption.tsx} +6 -1
  101. package/src/components/CalendarPage/formParts/CancelInsuranceText.tsx +105 -0
  102. package/src/components/CalendarPage/formParts/{DefaultBookingFields.js → DefaultBookingFields.ts} +3 -1
  103. package/src/components/CalendarPage/formParts/DiscountCode.tsx +62 -0
  104. package/src/components/CalendarPage/formParts/{Guests.js → Guests.tsx} +10 -4
  105. package/src/components/CalendarPage/formParts/{OptionalBookingFields.js → OptionalBookingFields.tsx} +0 -0
  106. package/src/components/CalendarPage/formParts/{OptionalCosts.js → OptionalCosts.tsx} +1 -2
  107. package/src/components/CalendarPage/formParts/{SuccessMessage.js → SuccessMessage.tsx} +0 -0
  108. package/src/components/CalendarPage/formParts/Validations.tsx +78 -0
  109. package/src/components/CalendarPage/formParts/{discount.js → discount.tsx} +11 -2
  110. package/src/components/CalendarPage/formParts/form_types.d.ts +38 -0
  111. package/src/components/CalendarPage/formParts/{insurances.js → insurances.tsx} +14 -10
  112. package/src/components/CalendarPage/formParts/{radioButtons.js → radioButtons.tsx} +0 -0
  113. package/src/components/Error/{ApiError.js → ApiError.tsx} +6 -4
  114. package/src/components/Error/{IntegrationError.js → IntegrationError.tsx} +17 -11
  115. package/src/components/Error/{index.js → index.ts} +0 -0
  116. package/src/components/{ErrorBoundary.js → ErrorBoundary.tsx} +13 -5
  117. package/src/components/Modal/index.tsx +46 -0
  118. package/src/components/ReviewsPage/Queries.ts +26 -0
  119. package/src/components/ReviewsPage/ReviewsPage.tsx +43 -0
  120. package/src/components/ReviewsPage/Score.tsx +25 -0
  121. package/src/components/ReviewsPage/SingleReview.tsx +38 -0
  122. package/src/components/SafeBooking.tsx +97 -0
  123. package/src/components/SearchPage/Field.tsx +75 -0
  124. package/src/components/SearchPage/Filters.tsx +91 -0
  125. package/src/components/SearchPage/Paginator.tsx +63 -0
  126. package/src/components/SearchPage/Results.tsx +129 -0
  127. package/src/components/SearchPage/{SearchPage.js → SearchPage.tsx} +42 -31
  128. package/src/components/SearchPage/{SingleResult.js → SingleResult.tsx} +15 -8
  129. package/src/components/SearchPage/filters/Categories.tsx +57 -0
  130. package/src/components/SearchPage/filters/DateFilter.tsx +34 -0
  131. package/src/components/SearchPage/filters/List.tsx +80 -0
  132. package/src/components/SearchPage/filters/NumberFilter.tsx +37 -0
  133. package/src/components/SearchPage/filters/Radio.tsx +46 -0
  134. package/src/components/SearchPage/filters/Select.tsx +85 -0
  135. package/src/components/SearchPage/filters/__tests__/helper.spec.js +15 -0
  136. package/src/components/SearchPage/filters/filter_types.d.ts +25 -0
  137. package/src/components/SearchPage/filters/helper.ts +19 -0
  138. package/src/components/icons/ArrowLeft.svg.tsx +20 -0
  139. package/src/components/icons/{ArrowRight.svg.js → ArrowRight.svg.tsx} +0 -0
  140. package/src/components/icons/{Reload.svg.js → Reload.svg.tsx} +0 -0
  141. package/src/components/icons/{info.svg.js → info.svg.tsx} +0 -0
  142. package/src/components/icons/{loading.svg.js → loading.svg.tsx} +1 -1
  143. package/src/custom.d.ts +10 -0
  144. package/src/index.tsx +93 -0
  145. package/src/locales/de.json +4 -3
  146. package/src/locales/en.json +4 -3
  147. package/src/locales/es.json +4 -3
  148. package/src/locales/fr.json +4 -3
  149. package/src/locales/it.json +4 -3
  150. package/src/locales/nl.json +4 -3
  151. package/src/styles/main.css +2 -1
  152. package/src/styles/modal.css +1 -1
  153. package/src/styles/pagination.css +25 -23
  154. package/src/styles/result.css +33 -2
  155. package/src/styles/reviews.css +76 -0
  156. package/src/types.d.ts +85 -0
  157. package/tsconfig.json +17 -0
  158. package/vite.config.ts +31 -0
  159. package/build/index.html +0 -16
  160. package/build/index.js +0 -48528
  161. package/cypress.json +0 -9
  162. package/rollup.config.js +0 -30
  163. package/src/_lib/format.js +0 -16
  164. package/src/components/App.js +0 -164
  165. package/src/components/CalendarPage/BookingForm.js +0 -57
  166. package/src/components/CalendarPage/Calendar.js +0 -373
  167. package/src/components/CalendarPage/CalendarHeader.js +0 -58
  168. package/src/components/CalendarPage/CalendarPage.js +0 -158
  169. package/src/components/CalendarPage/FormCreator.js +0 -278
  170. package/src/components/CalendarPage/FormItems/Wrapper.js +0 -0
  171. package/src/components/CalendarPage/PriceField.js +0 -203
  172. package/src/components/CalendarPage/Summary/Description.js +0 -22
  173. package/src/components/CalendarPage/Summary/Object.js +0 -46
  174. package/src/components/CalendarPage/Summary/RentAndDiscount.js +0 -21
  175. package/src/components/CalendarPage/Summary/index.js +0 -19
  176. package/src/components/CalendarPage/formParts/AssistanceMessage.js +0 -47
  177. package/src/components/CalendarPage/formParts/CancelInsuranceText.js +0 -91
  178. package/src/components/CalendarPage/formParts/DiscountCode.js +0 -62
  179. package/src/components/CalendarPage/formParts/summary.js +0 -43
  180. package/src/components/Modal/index.js +0 -58
  181. package/src/components/ReviewsPage/ReviewsPage.js +0 -15
  182. package/src/components/SafeBooking.js +0 -69
  183. package/src/components/SearchPage/Field.js +0 -241
  184. package/src/components/SearchPage/Filters.js +0 -108
  185. package/src/components/SearchPage/Paginator.js +0 -59
  186. package/src/components/SearchPage/Results.js +0 -130
  187. package/src/components/SearchPage/filters/List.js +0 -63
  188. package/src/components/icons/ArrowLeft.svg.js +0 -18
  189. package/src/index.js +0 -74
  190. package/webpack.config.dev.js +0 -53
  191. package/webpack.config.js +0 -67
@@ -0,0 +1,78 @@
1
+ import React from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+ import { HouseType } from '../../../types';
4
+ import { byString, validateAge } from './BookingHelpers';
5
+ import { isInt } from './OptionalBookingFields';
6
+
7
+ interface Props {
8
+ values: object;
9
+ house: HouseType;
10
+ bookingFields: [];
11
+ }
12
+
13
+ export function validateForm(values, house, bookingFields): [] {
14
+ const { babies_extra, persons } = house;
15
+
16
+ let errors = {};
17
+
18
+ let babies = Number(values.babies) - Number(babies_extra);
19
+ if (babies < 0) {
20
+ babies = 0;
21
+ }
22
+ values.persons = Number(values.children) + Number(values.adults) + babies;
23
+
24
+ for (let field of bookingFields) {
25
+ if (field.required) {
26
+ if (isInt(field.id)) {
27
+ const validateValue = byString(
28
+ values,
29
+ `extra_fields.booking_field_${field.id}`
30
+ );
31
+
32
+ if (!validateValue || validateValue === '') {
33
+ errors[field.id] = <FormattedMessage id="required" />;
34
+ }
35
+ } else {
36
+ const validateValue = byString(values, field.id);
37
+
38
+ if (!validateValue || validateValue === '') {
39
+ errors[field.id] = <FormattedMessage id="required" />;
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ if (values.adults < 1) {
46
+ errors.adults = <FormattedMessage id="at_least_1_adult" />;
47
+ }
48
+ if (Number(values.discount) > 0 && !values.discount_reason) {
49
+ errors.discount_reason = <FormattedMessage id="you_need_to_give_reason" />;
50
+ }
51
+ if (values.persons > persons) {
52
+ errors.max_persons = <FormattedMessage id="max_persons_reached" />;
53
+ }
54
+
55
+ if (
56
+ values.cancel_insurance !== 0 &&
57
+ validateAge(values.extra_fields?.date_of_birth)
58
+ ) {
59
+ errors['extra_fields.date_of_birth'] = (
60
+ <FormattedMessage id="at_least_18y_old" />
61
+ );
62
+ errors['insurances'] = <FormattedMessage id="at_least_18y_old" />;
63
+ }
64
+
65
+ if (
66
+ values.cancel_insurance !== 0 &&
67
+ !['nl', 'de', 'be'].includes(values.country)
68
+ ) {
69
+ errors['insurances'] = (
70
+ <FormattedMessage id="can_only_take_insurance_in_de_be_nl" />
71
+ );
72
+ errors['country'] = (
73
+ <FormattedMessage id="can_only_take_insurance_in_de_be_nl" />
74
+ );
75
+ }
76
+
77
+ return errors;
78
+ }
@@ -3,8 +3,17 @@ import PropTypes from 'prop-types';
3
3
  import { FormattedMessage } from 'react-intl';
4
4
  import { Field } from 'formik';
5
5
  import DiscountCode from './DiscountCode';
6
+ import { HouseType, PortalOptions } from '../../../types';
7
+ import { PossibleValues } from './form_types';
6
8
 
7
- const Discount = ({ errors, house, options, values }) => {
9
+ interface Props {
10
+ errors: object;
11
+ house: HouseType;
12
+ options: PortalOptions;
13
+ values: PossibleValues;
14
+ }
15
+
16
+ const Discount = ({ errors, house, options, values }: Props) => {
8
17
  if (
9
18
  (house.discounts && house.discounts !== '0') ||
10
19
  options.bookingForm?.showDiscountCode
@@ -52,7 +61,7 @@ const Discount = ({ errors, house, options, values }) => {
52
61
 
53
62
  Discount.propTypes = {
54
63
  house: PropTypes.object.isRequired,
55
- errors: PropTypes.object.isRequired,
64
+ errors: PropTypes.object.isRequired
56
65
  };
57
66
 
58
67
  export default Discount;
@@ -0,0 +1,38 @@
1
+ import { BuDate } from '../../../types';
2
+
3
+ export type PossibleValues = {
4
+ arrivalDate: BuDate;
5
+ departureDate: BuDate;
6
+ is_option: 'false' | 'true';
7
+ costs: any;
8
+ adults: number;
9
+ children: number;
10
+ babies: number;
11
+ persons: number;
12
+ discount: number;
13
+ country: number;
14
+ cancel_insurance: '0' | '1' | '2';
15
+ discount_code: string;
16
+ };
17
+
18
+ export type BookingFieldsType = SingleBookingFieldType[];
19
+
20
+ export type SingleBookingFieldType = {
21
+ id: string;
22
+ label: string;
23
+ type: InputTypes;
24
+ options?: string[];
25
+ required?: boolean;
26
+ mandatory?: boolean;
27
+ rows?: number;
28
+ placeholder?: string;
29
+ };
30
+
31
+ type InputTypes =
32
+ | 'textarea'
33
+ | 'text'
34
+ | 'email'
35
+ | 'date'
36
+ | 'country'
37
+ | 'select'
38
+ | 'booking_field';
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
- import PropTypes from 'prop-types';
3
2
  import { FormattedMessage } from 'react-intl';
4
3
  import { Field } from 'formik';
5
4
  import Modal from '../../Modal';
@@ -7,8 +6,15 @@ import Icon from '../../icons/info.svg';
7
6
  import CancelInsuranceText from './CancelInsuranceText';
8
7
  import { DateField } from '../FormItems';
9
8
  import { translatedOption } from './BookingHelpers'
9
+ import { HouseType } from '../../../types';
10
+ import { PossibleValues } from './form_types';
10
11
 
11
- function cancelInsurance(house) {
12
+ type Props = {
13
+ house: HouseType
14
+ values: PossibleValues
15
+ }
16
+
17
+ function cancelInsurance(house: HouseType) {
12
18
  if (house.cancel_insurance) {
13
19
  return (
14
20
  <div className="form-row inline">
@@ -17,9 +23,9 @@ function cancelInsurance(house) {
17
23
  </label>
18
24
  <Field component="select" name="cancel_insurance" required={true}>
19
25
  {translatedOption('choose', '')}
20
- {translatedOption('cancel_insurance_all_risk', 2)}
21
- {translatedOption('cancel_insurance_normal', 1)}
22
- {translatedOption('none', 0)}
26
+ {translatedOption('cancel_insurance_all_risk', '2')}
27
+ {translatedOption('cancel_insurance_normal', '1')}
28
+ {translatedOption('none', '0')}
23
29
  </Field>
24
30
  <Modal buttonText={<Icon />}>
25
31
  <CancelInsuranceText />
@@ -29,7 +35,7 @@ function cancelInsurance(house) {
29
35
  }
30
36
  }
31
37
 
32
- export const Insurances = ({ house, values }) => {
38
+ export const Insurances = ({ house, values }: Props) => {
33
39
  if (house.cancel_insurance) {
34
40
  return (
35
41
  <div className="form-section" id="insurances">
@@ -41,7 +47,8 @@ export const Insurances = ({ house, values }) => {
41
47
  <DateField
42
48
  label="extra_fields.date_of_birth"
43
49
  name="extra_fields.date_of_birth"
44
- required="true"
50
+ required
51
+ inline={false}
45
52
  description={
46
53
  <FormattedMessage id="insurance_company_needs_date_of_birth" />
47
54
  }
@@ -54,6 +61,3 @@ export const Insurances = ({ house, values }) => {
54
61
  }
55
62
  };
56
63
 
57
- Insurances.propTypes = {
58
- house: PropTypes.object.isRequired,
59
- };
@@ -1,10 +1,12 @@
1
1
  import React from 'react';
2
2
  import Modal from '../Modal';
3
-
4
3
  import { FormattedMessage } from 'react-intl';
4
+ import { ApolloError } from '@apollo/client';
5
5
 
6
- function ApiError(errors, modal) {
7
- console.error({ errors });
6
+ function ApiError(
7
+ errors: { errors: ApolloError },
8
+ modal: boolean
9
+ ): JSX.Element {
8
10
  const errorMessage = (
9
11
  <div className="bukazu-error-message">
10
12
  <h2>
@@ -25,7 +27,7 @@ function ApiError(errors, modal) {
25
27
  }
26
28
 
27
29
  ApiError.defaultProps = {
28
- modal: false,
30
+ modal: false
29
31
  };
30
32
 
31
33
  export default ApiError;
@@ -1,25 +1,33 @@
1
1
  import React from 'react';
2
+ import { FiltersType } from '../SearchPage/filters/filter_types';
3
+
4
+ interface Props {
5
+ portalCode: string;
6
+ pageType?: string;
7
+ locale?: string;
8
+ filters?: FiltersType;
9
+ }
2
10
 
3
11
  export default function IntegrationError({
4
12
  portalCode,
5
13
  pageType,
6
14
  locale,
7
- filters,
8
- }) {
9
- let errors = [];
15
+ filters
16
+ }: Props): JSX.Element | false {
17
+ let errors: string[] = [];
10
18
 
11
19
  if (!portalCode) {
12
20
  let message = 'No portal code is specified, so portal is not working';
13
21
  console.error(message);
14
22
  errors.push(message);
15
23
  }
16
-
24
+
17
25
  if (pageType && pageType !== 'reviews') {
18
26
  let message = `'${pageType}' is not a valid page`;
19
27
  console.error(message);
20
28
  errors.push(message);
21
29
  }
22
-
30
+
23
31
  if (!locale) {
24
32
  console.warn('No locale is set default to English');
25
33
  } else {
@@ -27,7 +35,7 @@ export default function IntegrationError({
27
35
  errors.push('Invalid locale');
28
36
  }
29
37
  }
30
-
38
+
31
39
  if (filters && !isObject(filters)) {
32
40
  let message = 'Filters variable is not an object';
33
41
  console.error(message, filters);
@@ -39,8 +47,7 @@ export default function IntegrationError({
39
47
 
40
48
  return (
41
49
  <div>
42
- <h2>
43
- Something went wrong please try again </h2>
50
+ <h2>Something went wrong please try again </h2>
44
51
  <ul>
45
52
  {errors.map((err) => (
46
53
  <li>{err}</li>
@@ -50,7 +57,6 @@ export default function IntegrationError({
50
57
  );
51
58
  }
52
59
 
53
-
54
- const isObject = (obj) => {
60
+ const isObject = (obj: any) => {
55
61
  return Object.prototype.toString.call(obj) === '[object Object]';
56
- };
62
+ };
File without changes
@@ -1,16 +1,24 @@
1
- import React from 'react';
1
+ import React, { ErrorInfo } from 'react';
2
2
 
3
- class ErrorBoundary extends React.Component {
4
- constructor(props) {
3
+ interface Props {
4
+ children: JSX.Element[];
5
+ }
6
+
7
+ type State = {
8
+ hasError: boolean;
9
+ };
10
+
11
+ class ErrorBoundary extends React.Component<Props, State> {
12
+ constructor(props: Props) {
5
13
  super(props);
6
14
  this.state = { hasError: false };
7
15
  }
8
16
 
9
- static getDerivedStateFromError(error) {
17
+ static getDerivedStateFromError() {
10
18
  // Update state so the next render will show the fallback UI.
11
19
  return { hasError: true };
12
20
  }
13
- componentDidCatch(error, errorInfo) {
21
+ componentDidCatch(error: Error, errorInfo: ErrorInfo) {
14
22
  // You can also log the error to an error reporting service
15
23
  console.log(error, errorInfo);
16
24
  }
@@ -0,0 +1,46 @@
1
+ import React, { useState } from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+
4
+ interface Props {
5
+ children: React.ReactNode
6
+ show?: boolean
7
+ buttonText?: string | React.ReactNode;
8
+ }
9
+
10
+ function Modal({ children, buttonText }: Props) {
11
+ const [visible, setVisible] = useState(false);
12
+
13
+ if (!visible) {
14
+ return (
15
+ <a className="info-button" onClick={() => setVisible(true)}>
16
+ {buttonText}
17
+ </a>
18
+ );
19
+ }
20
+
21
+ return (
22
+ <div className="bukazu-modal-container">
23
+ <div className="bukazu-modal-container-inner">
24
+ <div
25
+ className="bukazu-modal-escape"
26
+ onClick={() => setVisible(false)}
27
+ ></div>
28
+ <div className="bukazu-modal">
29
+ <div className="bukazu-modal-content">{children}</div>
30
+
31
+ <div className="bukazu-modal-footer">
32
+ <a onClick={() => setVisible(false)}>
33
+ <FormattedMessage id="close" />
34
+ </a>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ </div>
39
+ );
40
+ }
41
+
42
+ Modal.defaultProps = {
43
+ show: false
44
+ };
45
+
46
+ export default Modal;
@@ -0,0 +1,26 @@
1
+ import { gql } from '@apollo/client';
2
+
3
+ export const REVIEWS_QUERY = gql`
4
+ query ReviewPortalSiteQuery($id: ID!, $house_id: String!) {
5
+ PortalSite(id: $id) {
6
+ houses(house_code: $house_id) {
7
+ id
8
+ name
9
+ rating
10
+ scoreAmount
11
+ reviews {
12
+ id
13
+ name
14
+ review
15
+ score
16
+ createdAt
17
+ reviewCriteria {
18
+ id
19
+ name
20
+ score
21
+ }
22
+ }
23
+ }
24
+ }
25
+ }
26
+ `;
@@ -0,0 +1,43 @@
1
+ import { useQuery } from '@apollo/client';
2
+ import React, { useContext } from 'react';
3
+ import { FormattedMessage } from 'react-intl';
4
+ import { AppContext } from '../AppContext';
5
+ import { ApiError } from '../Error';
6
+ import Loading from '../icons/loading.svg';
7
+ import { REVIEWS_QUERY } from './Queries';
8
+ import Score from './Score';
9
+ import SingleReview from './SingleReview';
10
+
11
+ function ReviewsPage(): JSX.Element {
12
+ const { objectCode, portalCode } = useContext(AppContext);
13
+ const { data, error, loading } = useQuery(REVIEWS_QUERY, {
14
+ variables: { id: portalCode, house_id: objectCode }
15
+ });
16
+
17
+ if (loading)
18
+ return (
19
+ <div>
20
+ <Loading />
21
+ </div>
22
+ );
23
+ if (error) return <ApiError errors={error} />;
24
+
25
+ const house = data.PortalSite.houses[0];
26
+ const reviews = house.reviews;
27
+
28
+ return (
29
+ <div className="bu_reviews">
30
+ <div className="bu_reviews__overview">
31
+ <Score rating={house.rating} />
32
+ <div className="bu_reviews__overview__number">
33
+ {house.scoreAmount} <FormattedMessage id="reviews" />
34
+ </div>
35
+ </div>
36
+ {reviews.map((review) => {
37
+ return <SingleReview review={review} key={review.id} />;
38
+ })}
39
+ </div>
40
+ );
41
+ }
42
+
43
+ export default ReviewsPage;
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+
3
+ interface Props {
4
+ rating: number;
5
+ name?: string;
6
+ }
7
+
8
+ function Score({ rating, name }: Props) {
9
+ let color = 'low';
10
+ if (rating > 7) {
11
+ color = 'best';
12
+ } else if (rating > 6) {
13
+ color = 'good';
14
+ } else if (rating > 4) {
15
+ color = 'medium';
16
+ }
17
+ return (
18
+ <div className="bu_score">
19
+ <div className={`bu_score__rating ${color}`}>{rating.toFixed(1)} /10</div>
20
+ <div>{name}</div>
21
+ </div>
22
+ );
23
+ }
24
+
25
+ export default Score;
@@ -0,0 +1,38 @@
1
+ import React, { memo } from 'react';
2
+ import Score from './Score';
3
+
4
+ interface Props {
5
+ review: {
6
+ score: number;
7
+ name: string;
8
+ createdAt: string;
9
+ review: string;
10
+ reviewCriteria: {
11
+ score: number;
12
+ name: string;
13
+ id: number;
14
+ }[];
15
+ };
16
+ }
17
+
18
+ function SingleReview({ review }: Props): JSX.Element {
19
+ return (
20
+ <div className="bu_single_review">
21
+ <div className="bu_review_summary">
22
+ <Score rating={review.score} />
23
+ <div className="bu_review_summary__date_name">
24
+ <div>{review.createdAt},&nbsp;</div>
25
+ <div className="bu_review_summary__name">{review.name}</div>
26
+ </div>
27
+ </div>
28
+ <blockquote className="bu_review">{review.review}</blockquote>
29
+ <div className="bu_criteria">
30
+ {review.reviewCriteria.map((crit) => (
31
+ <Score rating={crit.score} name={crit.name} key={crit.id} />
32
+ ))}
33
+ </div>
34
+ </div>
35
+ );
36
+ }
37
+
38
+ export default memo(SingleReview);
@@ -0,0 +1,97 @@
1
+ import React, { CSSProperties, useContext } from 'react';
2
+ import pjson from '../../package.json';
3
+ import { AppContext } from './AppContext';
4
+
5
+ const style: CSSProperties = {
6
+ width: '100%',
7
+ padding: '16px',
8
+ display: 'flex',
9
+ justifyContent: 'center',
10
+ position: 'relative'
11
+ };
12
+ const styleLink = {
13
+ color: '#808080',
14
+ fontSize: 14,
15
+ textDecoration: 'none'
16
+ };
17
+
18
+ const trans: Trans = {
19
+ nl: {
20
+ url: 'http://bukazu.com/veiligheid',
21
+ label: 'Beveiligd en mogelijk gemaakt door BUKAZU'
22
+ },
23
+ en: {
24
+ url: 'http://bukazu.com/en/security',
25
+ label: 'Secured and made possible by BUKAZU'
26
+ },
27
+ de: {
28
+ url: 'http://bukazu.com/de/sicherheit',
29
+ label: 'Gesichert und ermöglicht durch BUKAZU'
30
+ },
31
+ fr: {
32
+ url: 'http://bukazu.com/fr/securite',
33
+ label: 'Sécurisé et rendu possible par BUKAZU'
34
+ },
35
+ es: {
36
+ url: 'http://bukazu.com/es/seguridad',
37
+ label: 'Asegurado y hecho posible por BUKAZU'
38
+ },
39
+ it: {
40
+ url: 'http://bukazu.com/it/sicurezza',
41
+ label: 'Protetto e reso possibile da BUKAZU'
42
+ }
43
+ };
44
+
45
+ function SafeBooking(): JSX.Element {
46
+ const { locale } = useContext(AppContext);
47
+
48
+ return (
49
+ <div style={style}>
50
+ <a href={trans[locale].url} style={styleLink}>
51
+ <svg
52
+ xmlns="http://www.w3.org/2000/svg"
53
+ xmlnsXlink="http://www.w3.org/1999/xlink"
54
+ version="1.1"
55
+ x="0px"
56
+ y="0px"
57
+ viewBox="0 0 100 100"
58
+ enableBackground="new 0 0 100 100"
59
+ xmlSpace="preserve"
60
+ width="16px"
61
+ height="16px"
62
+ style={{ marginRight: '4px', fill: '#808080' }}
63
+ >
64
+ <path d="M75.98,41.62h-2.47L73.5,29.31C73.49,15.9,62.58,4.99,49.17,5C35.76,5.01,24.85,15.92,24.86,29.33l0.02,12.31H24 c-4.61,0.01-8.35,3.75-8.34,8.36v36.65c0,4.61,3.75,8.35,8.36,8.35L76,94.97c4.61,0,8.35-3.74,8.34-8.35V49.96 C84.34,45.35,80.59,41.62,75.98,41.62z M33.84,41.64l-0.02-12.31c0-8.47,6.88-15.36,15.35-15.37c8.47,0,15.36,6.89,15.36,15.35 l0.02,12.31L33.84,41.64z" />
65
+ </svg>
66
+ {trans[locale].label}{' '}
67
+ <span
68
+ style={{
69
+ opacity: 0.5,
70
+ fontSize: 9,
71
+ position: 'absolute',
72
+ right: 10,
73
+ bottom: 0
74
+ }}
75
+ >
76
+ {/* v{pjson.version} */}
77
+ </span>
78
+ </a>
79
+ </div>
80
+ );
81
+ }
82
+
83
+ type Trans = {
84
+ nl: LocaleType;
85
+ en: LocaleType;
86
+ de: LocaleType;
87
+ es: LocaleType;
88
+ fr: LocaleType;
89
+ it: LocaleType;
90
+ };
91
+
92
+ type LocaleType = {
93
+ label: string;
94
+ url: string;
95
+ };
96
+
97
+ export default SafeBooking;
@@ -0,0 +1,75 @@
1
+ import React from 'react';
2
+ import List from './filters/List';
3
+ import { createNumberArray, createPriceArray } from './filters/helper';
4
+ import Select from './filters/Select';
5
+ import Categories from './filters/Categories';
6
+ import Radio from './filters/Radio';
7
+ import DateFilter from './filters/DateFilter';
8
+ import NumberFilter from './filters/NumberFilter';
9
+ import { PortalSiteType } from '../../types';
10
+ import { Field as FieldType, FiltersType } from './filters/filter_types';
11
+
12
+ interface Props {
13
+ PortalSite: PortalSiteType;
14
+ field: FieldType;
15
+ filters: FiltersType;
16
+ value: string;
17
+ onFilterChange: Function;
18
+ }
19
+
20
+ function Field({ PortalSite, field, filters, value, onFilterChange }:Props):JSX.Element {
21
+ let options = [];
22
+ if (['countries', 'cities', 'regions'].includes(field.id)) {
23
+ options = PortalSite[field.id];
24
+ } else if (field.id === 'persons_min' || field.id === 'persons_max') {
25
+ options = createNumberArray(PortalSite.max_persons);
26
+ } else if (field.id === 'bedrooms_min') {
27
+ options = createNumberArray(PortalSite.max_bedrooms);
28
+ } else if (field.id === 'bathrooms_min') {
29
+ options = createNumberArray(PortalSite.max_bathrooms);
30
+ } else if (field.id === 'weekprice_max') {
31
+ options = createPriceArray(PortalSite.max_weekprice);
32
+ } else {
33
+ options = createNumberArray(PortalSite[field.id]);
34
+ }
35
+
36
+ let default_settings = {
37
+ options,
38
+ field,
39
+ filters,
40
+ value
41
+ };
42
+
43
+ if (field.id === 'properties') {
44
+ return (
45
+ <Categories
46
+ PortalSite={PortalSite}
47
+ filters={filters}
48
+ onChange={onFilterChange}
49
+ />
50
+ );
51
+ } else if (field.type === 'select') {
52
+ return <Select {...default_settings} onChange={onFilterChange} />;
53
+ } else if (field.type === 'list') {
54
+ return <List {...default_settings} onChange={onFilterChange} />;
55
+ } else if (field.type === 'radio') {
56
+ return <Radio {...default_settings} onChange={onFilterChange} />;
57
+ } else if (field.type === 'number') {
58
+ return (
59
+ <NumberFilter PortalSite={PortalSite} field={field} value={value} onChange={onFilterChange} />
60
+ );
61
+ } else if (field.type === 'date') {
62
+ return <DateFilter field={field} value={value} onChange={onFilterChange} />;
63
+ } else {
64
+ return (
65
+ <input
66
+ value={value}
67
+ onBlur={(event) => {
68
+ onFilterChange(field.id, event.target.value);
69
+ }}
70
+ />
71
+ );
72
+ }
73
+ }
74
+
75
+ export default Field;