bukazu-portal-react 2.1.21 → 3.0.3

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,91 @@
1
+ import React, { useState } from 'react';
2
+ import Field from './Field';
3
+ import Reload from '../icons/Reload.svg';
4
+ import { FormattedMessage } from 'react-intl';
5
+ import { defaultFilter } from './filters/helper';
6
+ import { FiltersType } from './filters/filter_types';
7
+ import { PortalOptions, PortalSiteType } from '../../types';
8
+
9
+ interface Props {
10
+ filters: FiltersType;
11
+ onFilterChange: Function;
12
+ PortalSite: PortalSiteType;
13
+ options: PortalOptions;
14
+ }
15
+
16
+ function Filters({
17
+ filters,
18
+ onFilterChange,
19
+ PortalSite,
20
+ options
21
+ }: Props): JSX.Element {
22
+ function saveFilters(field, input) {
23
+ let newFilters: any = filters;
24
+ newFilters[field] = input;
25
+ onFilterChange(newFilters);
26
+ }
27
+
28
+ const [show, setShow] = useState(false);
29
+
30
+ const searchFields = options.searchFields || defaultFilter;
31
+ let fixed = options.filtersForm
32
+ ? options.filtersForm.fixedMobile
33
+ ? 'fixed-mobile'
34
+ : null
35
+ : null;
36
+
37
+ let filterClass = options.filtersForm
38
+ ? options.filtersForm.show
39
+ ? `filters filters-${options.filtersForm.location}`
40
+ : 'filters-hidden'
41
+ : 'filters';
42
+
43
+ let showOn = show && 'showOnMobile';
44
+
45
+ return (
46
+ <>
47
+ <button
48
+ className={`filters-button ${fixed}`}
49
+ onClick={() => setShow(!show)}
50
+ >
51
+ <FormattedMessage id="filters" />
52
+ </button>
53
+ <div className={`${filterClass} ${fixed} ${showOn}`}>
54
+ <button
55
+ onClick={() => {
56
+ let filters = {};
57
+ for (var property in filters) {
58
+ filters[property] = '';
59
+ }
60
+ onFilterChange(filters);
61
+ }}
62
+ className="filters-reload"
63
+ >
64
+ <Reload />
65
+ </button>
66
+ {searchFields.map((field) => (
67
+ <div key={field.id} className="bu-field" id={field.id}>
68
+ <label
69
+ style={{
70
+ width: '100%',
71
+ display: 'block'
72
+ }}
73
+ htmlFor={field.id}
74
+ >
75
+ {PortalSite[`${field.id}_label`]}
76
+ </label>
77
+ <Field
78
+ field={field}
79
+ PortalSite={PortalSite}
80
+ filters={filters}
81
+ value={filters[field.id]}
82
+ onFilterChange={saveFilters}
83
+ />
84
+ </div>
85
+ ))}
86
+ </div>
87
+ </>
88
+ );
89
+ }
90
+
91
+ export default Filters;
@@ -0,0 +1,63 @@
1
+ import React from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+ import { HOUSE_COUNT_QUERY } from '../../_lib/SearchQueries';
4
+ import Loading from '../icons/loading.svg';
5
+ import ReactPaginate from 'react-paginate';
6
+ import { useQuery } from '@apollo/client';
7
+
8
+ interface Props {
9
+ onPageChange: Function;
10
+ variables: object;
11
+ activePage: number;
12
+ limit: number;
13
+ }
14
+
15
+ function Paginator({
16
+ onPageChange,
17
+ variables,
18
+ activePage,
19
+ limit
20
+ }: Props): JSX.Element {
21
+ const { loading, error, data } = useQuery(HOUSE_COUNT_QUERY, { variables });
22
+
23
+ if (loading)
24
+ return (
25
+ <div
26
+ style={{
27
+ width: '100%',
28
+ display: 'flex',
29
+ justifyContent: 'center'
30
+ }}
31
+ >
32
+ <Loading />
33
+ </div>
34
+ );
35
+ if (error) {
36
+ return <div>Error</div>;
37
+ }
38
+
39
+ const results = data.PortalSite.houses;
40
+
41
+ const pageCount = Math.ceil(results.length / limit);
42
+ return (
43
+ <div className="bu-paginator">
44
+ <div>
45
+ {results.length} <FormattedMessage id="results" />
46
+ </div>
47
+ <ReactPaginate
48
+ pageCount={pageCount}
49
+ onPageChange={({ selected }) => {
50
+ onPageChange(selected);
51
+ }}
52
+ forcePage={activePage}
53
+ pageRangeDisplayed={5}
54
+ breakLabel="..."
55
+ className="bu-pagination"
56
+ nextLabel=">"
57
+ previousLabel="<"
58
+ />
59
+ </div>
60
+ );
61
+ }
62
+
63
+ export default Paginator;
@@ -0,0 +1,129 @@
1
+ import React, { useContext } from 'react';
2
+ import { FormattedMessage } from 'react-intl';
3
+ import { differenceInCalendarDays } from 'date-fns';
4
+ import Loading from '../icons/loading.svg';
5
+ import SingleResult from './SingleResult';
6
+ import Paginator from './Paginator';
7
+
8
+ import { HOUSES_PRICE_QUERY, HOUSES_QUERY } from '../../_lib/SearchQueries';
9
+ import { ApiError } from '../Error';
10
+ import { useQuery } from '@apollo/client';
11
+ import { FiltersType } from './filters/filter_types';
12
+ import { Parse_EN_US } from '../../_lib/date_helper';
13
+ import { AppContext } from '../AppContext';
14
+ import { HouseType, PortalSiteType } from '../../types';
15
+
16
+ interface Props {
17
+ filters: FiltersType;
18
+ PortalSite: PortalSiteType;
19
+ limit: number;
20
+ skip: number;
21
+ onPageChange: Function;
22
+ activePage: number;
23
+ }
24
+
25
+ function Results({
26
+ filters,
27
+ PortalSite,
28
+ limit,
29
+ skip,
30
+ onPageChange,
31
+ activePage
32
+ }: Props): JSX.Element {
33
+ const { portalCode } = useContext(AppContext);
34
+
35
+ let min_nights = null;
36
+ let requestPrices = false;
37
+ if (filters.departure_date && filters.arrival_date) {
38
+ min_nights = differenceInCalendarDays(
39
+ Parse_EN_US(filters.departure_date),
40
+ Parse_EN_US(filters.arrival_date)
41
+ );
42
+ requestPrices = true;
43
+ } else if (filters.arrival_date) {
44
+ min_nights = 1;
45
+ }
46
+ let filterProperties = filters.properties || [];
47
+ filterProperties = filterProperties.map((e) => {
48
+ return JSON.stringify(e);
49
+ });
50
+
51
+ let properties = filterProperties.join(',');
52
+
53
+ let variables = {
54
+ id: portalCode,
55
+ country_id: filters.countries || null,
56
+ region_id: filters.regions || null,
57
+ city_id: filters.cities,
58
+ persons_min: Number(filters.persons_min) || null,
59
+ persons_max: Number(filters.persons_max) || null,
60
+ bedrooms_min: Number(filters.bedrooms_min),
61
+ bathrooms_min: Number(filters.bathrooms_min),
62
+ arrival_date: filters.arrival_date,
63
+ starts_at: filters.arrival_date,
64
+ ends_at: filters.departure_date,
65
+ no_nights: Number(min_nights) || null,
66
+ extra_search: filters.extra_search,
67
+ properties,
68
+ weekprice_max: Number(filters.weekprice_max) || null,
69
+ limit,
70
+ skip
71
+ };
72
+
73
+ const { loading, error, data } = useQuery(
74
+ requestPrices ? HOUSES_PRICE_QUERY : HOUSES_QUERY,
75
+ { variables }
76
+ );
77
+
78
+ if (loading)
79
+ return (
80
+ <div>
81
+ <Loading />
82
+ </div>
83
+ );
84
+ if (error) {
85
+ return (
86
+ <div>
87
+ <ApiError errors={error}></ApiError>
88
+ </div>
89
+ );
90
+ }
91
+
92
+ const Pagination = (
93
+ <Paginator
94
+ variables={variables}
95
+ activePage={activePage}
96
+ limit={limit}
97
+ onPageChange={onPageChange}
98
+ />
99
+ );
100
+ const Results: HouseType[] = data.PortalSite.houses;
101
+
102
+ return (
103
+ <div
104
+ id="results"
105
+ className={
106
+ PortalSite.options.filtersForm
107
+ ? PortalSite.options.filtersForm.mode
108
+ : ''
109
+ }
110
+ >
111
+ {Pagination}
112
+ {Results.length === 0 ? (
113
+ <div className="bu-noresults">
114
+ <FormattedMessage id="no_results" />
115
+ </div>
116
+ ) : null}
117
+ {Results.map((result) => (
118
+ <SingleResult
119
+ key={result.id}
120
+ result={result}
121
+ options={PortalSite.options.filtersForm}
122
+ />
123
+ ))}
124
+ {Pagination}
125
+ </div>
126
+ );
127
+ }
128
+
129
+ export default Results;
@@ -1,58 +1,77 @@
1
1
  import React, { Component } from 'react';
2
- import PropTypes from 'prop-types';
3
2
  import Filters from './Filters';
4
3
  import Results from './Results';
4
+ import { PortalOptions, PortalSiteType } from '../../types';
5
+ import { FiltersType } from './filters/filter_types';
5
6
 
6
- class SearchPage extends Component {
7
- constructor(props) {
7
+ type MyProps = {
8
+ options: PortalOptions;
9
+ filters?: FiltersType;
10
+ PortalSite: PortalSiteType;
11
+ locale: string;
12
+ };
13
+
14
+ type MyState = {
15
+ filters: FiltersType;
16
+ activePage: number;
17
+ limit: number;
18
+ skip: number;
19
+ };
20
+
21
+ class SearchPage extends Component<MyProps, MyState> {
22
+ constructor(props: MyProps) {
8
23
  super(props);
9
24
  let limit = this.props.options.filtersForm
10
25
  ? Number(this.props.options.filtersForm.no_results)
11
26
  : 20;
12
27
  this.state = {
13
- filters: this.props.filters,
28
+ filters: this.props.filters || {},
14
29
  activePage: 1,
15
30
  limit,
16
- skip: 0,
31
+ skip: 0
17
32
  };
18
33
  this.onFilterChange = this.onFilterChange.bind(this);
19
34
  this.pageChange = this.pageChange.bind(this);
20
35
  }
21
36
 
22
37
  componentDidMount() {
23
- let filters = localStorage.getItem('bukazuFilters')
24
- let activePage = localStorage.getItem('bukazuActivePage')
38
+ let filters = localStorage.getItem('bukazuFilters');
39
+ let activePage = localStorage.getItem('bukazuActivePage');
25
40
 
26
- this.setState({
27
- filters: JSON.parse(filters) || this.props.filters,
28
- });
29
- this.pageChange(activePage || 1)
41
+ if (filters) {
42
+ this.setState({
43
+ filters: JSON.parse(filters)
44
+ });
45
+ }
46
+ if (activePage) {
47
+ this.pageChange(parseInt(activePage) || 0);
48
+ }
30
49
  }
31
50
 
32
- onFilterChange(data) {
51
+ onFilterChange(data: FiltersType) {
33
52
  let filters = data;
34
53
  this.setState({
35
- filters,
54
+ filters
36
55
  });
37
56
 
38
57
  localStorage.setItem('bukazuFilters', JSON.stringify(filters));
39
- this.pageChange(1);
58
+ this.pageChange(0);
40
59
  }
41
60
 
42
- pageChange(pageNumber) {
61
+ pageChange(pageNumber: number) {
43
62
  const { limit } = this.state;
44
- let newSkip = pageNumber * limit - limit;
63
+ let newSkip = pageNumber * limit;
45
64
 
46
- localStorage.setItem('bukazuActivePage', pageNumber)
65
+ localStorage.setItem('bukazuActivePage', pageNumber.toString());
47
66
  this.setState({
48
67
  activePage: pageNumber,
49
- skip: newSkip,
68
+ skip: newSkip
50
69
  });
51
70
  }
52
71
 
53
72
  render() {
54
73
  const { filters, activePage, limit, skip } = this.state;
55
- const { options, locale } = this.props;
74
+ const { options, PortalSite } = this.props;
56
75
 
57
76
  return (
58
77
  <div
@@ -63,21 +82,20 @@ class SearchPage extends Component {
63
82
  ? 'bu-reverse'
64
83
  : options.filtersForm.location === 'top'
65
84
  ? 'bu-column'
66
- : null
67
- : null
85
+ : ''
86
+ : ''
68
87
  }
69
88
  >
70
89
  <Filters
71
- PortalSite={this.props.PortalSite}
90
+ PortalSite={PortalSite}
72
91
  filters={filters}
73
92
  onFilterChange={this.onFilterChange}
74
93
  options={options}
75
94
  />
76
95
  <Results
77
- PortalSite={this.props.PortalSite}
96
+ PortalSite={PortalSite}
78
97
  filters={filters}
79
98
  activePage={activePage}
80
- locale={locale}
81
99
  onPageChange={this.pageChange}
82
100
  skip={skip}
83
101
  limit={limit}
@@ -87,11 +105,4 @@ class SearchPage extends Component {
87
105
  }
88
106
  }
89
107
 
90
- SearchPage.propTypes = {
91
- PortalSite: PropTypes.object.isRequired,
92
- options: PropTypes.object.isRequired,
93
- locale: PropTypes.string.isRequired,
94
- filters: PropTypes.object.isRequired,
95
- };
96
-
97
108
  export default SearchPage;
@@ -1,9 +1,14 @@
1
1
  import React from 'react';
2
- import PropTypes from 'prop-types';
3
2
  import { FormattedMessage, FormattedNumber } from 'react-intl';
3
+ import { FiltersFormType, HouseType } from '../../types';
4
4
  import ArrowRight from '../icons/ArrowRight.svg';
5
5
 
6
- function SingleResult({ result, options }) {
6
+ interface Props {
7
+ result: HouseType;
8
+ options: FiltersFormType;
9
+ }
10
+
11
+ function SingleResult({ result, options }: Props): JSX.Element {
7
12
  let thisOptions = options || {};
8
13
 
9
14
  return (
@@ -40,11 +45,18 @@ function SingleResult({ result, options }) {
40
45
  </div>
41
46
  )}
42
47
  </div>
48
+ {thisOptions.showRating && result.rating && (
49
+ <div className="result-rating">
50
+ <div className="result-rating-inner">
51
+ {result.rating.toFixed(1)}
52
+ </div>
53
+ </div>
54
+ )}
43
55
  {thisOptions.showPrice && (
44
56
  <div className="result-price">
45
57
  {result.booking_price ? (
46
58
  <>
47
- <FormattedMessage id="price_from" />
59
+ <FormattedMessage id="price_from" />
48
60
  <span className="price">
49
61
  €{' '}
50
62
  <FormattedNumber
@@ -78,9 +90,4 @@ function SingleResult({ result, options }) {
78
90
  );
79
91
  }
80
92
 
81
- SingleResult.propTypes = {
82
- result: PropTypes.object.isRequired,
83
- options: PropTypes.object.isRequired,
84
- };
85
-
86
93
  export default SingleResult;
@@ -0,0 +1,57 @@
1
+ import React, { SyntheticEvent } from 'react';
2
+ import { PortalSiteType } from '../../../types';
3
+ import { FiltersType } from './filter_types';
4
+
5
+ interface Props {
6
+ PortalSite: PortalSiteType;
7
+ filters: FiltersType;
8
+ onChange: Function;
9
+ }
10
+
11
+ function Categories({ PortalSite, filters, onChange }: Props): JSX.Element[] {
12
+ const properties = filters.properties || [];
13
+ let requiredCategories = PortalSite.options.filtersForm.categories;
14
+ let input: JSX.Element[] = [];
15
+
16
+ const handleChange = (event: SyntheticEvent<any>) => {
17
+ const value = Number(event.currentTarget.value);
18
+
19
+ if (properties.includes(value)) {
20
+ let index = properties.indexOf(value);
21
+ properties.splice(index, 1);
22
+ } else {
23
+ properties.push(value);
24
+ }
25
+ onChange('properties', properties);
26
+ };
27
+
28
+ PortalSite.categories.map((category) => {
29
+ if (requiredCategories.includes(category.id)) {
30
+ input.push(
31
+ <div className="bu-properties" key={category.id}>
32
+ <strong>{category.name}</strong>
33
+ <ul>
34
+ {category.properties.map((property) => (
35
+ <li key={property.id}>
36
+ <label htmlFor={property.id.toString()}>
37
+ <input
38
+ type="checkbox"
39
+ id={property.id.toString()}
40
+ value={property.id}
41
+ checked={properties.includes(property.id)}
42
+ onChange={handleChange}
43
+ />
44
+ {property.name}
45
+ </label>
46
+ </li>
47
+ ))}
48
+ </ul>
49
+ </div>
50
+ );
51
+ }
52
+ });
53
+
54
+ return input;
55
+ }
56
+
57
+ export default Categories;
@@ -0,0 +1,34 @@
1
+ import { format } from 'date-fns';
2
+ import React from 'react';
3
+ import DatePicker from 'react-date-picker';
4
+ import { Field } from './filter_types';
5
+
6
+ interface Props {
7
+ value: any;
8
+ onChange: Function;
9
+ field: Field;
10
+ }
11
+
12
+ function DateFilter({ value, onChange, field }: Props): JSX.Element {
13
+ let tempval;
14
+ if (value === '' || !value) {
15
+ tempval = null;
16
+ } else {
17
+ tempval = new Date(value);
18
+ }
19
+ return (
20
+ <DatePicker
21
+ onChange={(date: Date) => {
22
+ if (date) {
23
+ onChange(field.id, format(date, 'yyyy-MM-dd'));
24
+ } else {
25
+ onChange(field.id, '');
26
+ }
27
+ }}
28
+ value={tempval}
29
+ format="dd-MM-y"
30
+ />
31
+ );
32
+ }
33
+
34
+ export default DateFilter;
@@ -0,0 +1,80 @@
1
+ import React from 'react';
2
+ import { Field, FiltersType, OptionsType } from './filter_types';
3
+
4
+ interface Props {
5
+ field: Field;
6
+ options: OptionsType[];
7
+ filters: FiltersType;
8
+ value: string;
9
+ onChange: Function;
10
+ }
11
+
12
+ export default function List({
13
+ filters,
14
+ field,
15
+ options,
16
+ onChange,
17
+ value
18
+ }: Props): JSX.Element {
19
+ const countries = filters.countries;
20
+
21
+ const updateList = (e: { target: { value: string } }) => {
22
+ if (value === e.target.value) {
23
+ handleChange(null);
24
+ } else {
25
+ handleChange(e.target.value);
26
+ }
27
+ };
28
+
29
+ const handleChange = ( value: string ) => {
30
+ onChange(field.id, value)
31
+ }
32
+
33
+ if (['cities', 'regions'].includes(field.id)) {
34
+ return (
35
+ <ul className="radioList">
36
+ {options.map((opt) => (
37
+ <li
38
+ key={opt.id}
39
+ className={`bu-list-item ${
40
+ countries && !countries.includes(opt.country_id)
41
+ ? 'bu-disabled'
42
+ : 'bu-open'
43
+ }`}
44
+ >
45
+ <input
46
+ name={field.id}
47
+ type="checkbox"
48
+ id={opt.id}
49
+ value={opt.id}
50
+ disabled={countries ? !countries.includes(opt.country_id) : false}
51
+ checked={value === opt.id}
52
+ onBlur={handleChange}
53
+ onChange={handleChange}
54
+ />
55
+ <label htmlFor={opt.id}>{opt.name}</label>
56
+ </li>
57
+ ))}
58
+ </ul>
59
+ );
60
+ } else {
61
+ return (
62
+ <ul className="radioList">
63
+ {options.map((opt) => (
64
+ <li key={opt.id} className={`bu-list-item bu-open`}>
65
+ <input
66
+ name={field.id}
67
+ type="checkbox"
68
+ id={opt.id}
69
+ value={opt.id}
70
+ checked={value === opt.id}
71
+ onBlur={handleChange}
72
+ onChange={updateList}
73
+ />
74
+ <label htmlFor={opt.id}>{opt.name}</label>
75
+ </li>
76
+ ))}
77
+ </ul>
78
+ );
79
+ }
80
+ }
@@ -0,0 +1,37 @@
1
+ import React, { SyntheticEvent } from 'react';
2
+ import { PortalSiteType } from '../../../types';
3
+ import { Field } from './filter_types';
4
+
5
+ interface Props {
6
+ PortalSite: PortalSiteType;
7
+ field: Field;
8
+ value: string;
9
+ onChange: Function;
10
+ }
11
+
12
+ function NumberFilter({
13
+ PortalSite,
14
+ field,
15
+ value,
16
+ onChange
17
+ }: Props): JSX.Element {
18
+ const handleChange = (event: SyntheticEvent<any>) => {
19
+ onChange(field.id, event.currentTarget.value);
20
+ };
21
+
22
+ return (
23
+ <input
24
+ value={value}
25
+ type="number"
26
+ min="0"
27
+ max={
28
+ field.id === 'persons_min'
29
+ ? PortalSite.max_persons
30
+ : PortalSite[field.id]
31
+ }
32
+ onBlur={handleChange}
33
+ />
34
+ );
35
+ }
36
+
37
+ export default NumberFilter