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.
- package/.github/workflows/dependabot.yml +11 -0
- package/.github/workflows/node.js.yml +31 -0
- package/.prettierrc +3 -6
- package/CHANGELOG.MD +5 -0
- package/babel.config.json +1 -1
- package/build/index.css +1 -2312
- package/build/portal.es.js +35483 -0
- package/build/portal.umd.js +596 -0
- package/{build/calendar.html → calendar.html} +2 -4
- package/coverage/clover.xml +28 -0
- package/coverage/coverage-final.json +2 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/helper.ts.html +142 -0
- package/coverage/lcov-report/index.html +116 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +196 -0
- package/coverage/lcov.info +36 -0
- package/cypress/{integration → e2e}/.examples/actions.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/aliasing.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/assertions.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/connectors.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/cookies.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/cypress_api.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/files.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/local_storage.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/location.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/misc.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/navigation.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/network_requests.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/querying.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/spies_stubs_clocks.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/traversal.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/utilities.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/viewport.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/waiting.spec.js +0 -0
- package/cypress/{integration → e2e}/.examples/window.spec.js +0 -0
- package/cypress/{integration → e2e}/booking.spec.js +0 -0
- package/cypress/{integration → e2e}/calendar.spec.js +0 -0
- package/cypress/{integration → e2e}/search.spec.js +0 -0
- package/cypress/support/commands.ts +37 -0
- package/cypress/support/component-index.html +12 -0
- package/cypress/support/component.ts +39 -0
- package/cypress/support/{index.js → e2e.js} +0 -0
- package/cypress.config.ts +15 -0
- package/{dev.js → dev.tsx} +1 -1
- package/index.html +15 -0
- package/{build/invalid-calendar.html → invalid-calendar.html} +0 -0
- package/jest.config.js +195 -0
- package/package.json +35 -40
- package/reviews.html +16 -0
- package/src/_lib/{SearchQueries.js → SearchQueries.ts} +8 -2
- package/src/_lib/{countries.js → countries.ts} +0 -0
- package/src/_lib/date_helper.ts +27 -0
- package/src/_lib/{queries.js → queries.ts} +24 -5
- package/src/components/App.tsx +132 -0
- package/src/components/AppContext.ts +14 -0
- package/src/components/CalendarPage/BookingForm.tsx +42 -0
- package/src/components/CalendarPage/Calendar.tsx +50 -0
- package/src/components/CalendarPage/CalendarPage.tsx +43 -0
- package/src/components/CalendarPage/CalendarParts/CalendarContext.tsx +89 -0
- package/src/components/CalendarPage/CalendarParts/CalendarHeader.tsx +72 -0
- package/src/components/CalendarPage/CalendarParts/DayClasses.ts +111 -0
- package/src/components/CalendarPage/CalendarParts/GenerateCalendar.tsx +64 -0
- package/src/components/CalendarPage/CalendarParts/Legend.tsx +33 -0
- package/src/components/CalendarPage/CalendarParts/MonthHeader.tsx +15 -0
- package/src/components/CalendarPage/CalendarParts/Months.tsx +37 -0
- package/src/components/CalendarPage/CalendarParts/RenderCells.tsx +94 -0
- package/src/components/CalendarPage/CalendarParts/SingleMonth.tsx +72 -0
- package/src/components/CalendarPage/CalendarParts/StartBooking.tsx +17 -0
- package/src/components/CalendarPage/CalendarParts/WeekDays.tsx +27 -0
- package/src/components/CalendarPage/FormCreator.tsx +213 -0
- package/src/components/CalendarPage/FormItems/{Date.js → Date.tsx} +10 -2
- package/src/components/CalendarPage/FormItems/{NumberSelect.js → NumberSelect.tsx} +1 -1
- package/src/components/CalendarPage/FormItems/{Select.js → Select.tsx} +0 -0
- package/src/components/CalendarPage/FormItems/{index.js → index.ts} +0 -0
- package/src/components/CalendarPage/PriceField/Price.tsx +58 -0
- package/src/components/CalendarPage/PriceField/Queries.ts +23 -0
- package/src/components/CalendarPage/PriceField/index.tsx +127 -0
- package/src/components/CalendarPage/Summary/{CostRow.js → CostRow.tsx} +19 -3
- package/src/components/CalendarPage/Summary/{CostSection.js → CostSection.tsx} +5 -1
- package/src/components/CalendarPage/Summary/{CostSummary.js → CostSummary.tsx} +19 -10
- package/src/components/CalendarPage/Summary/Description.tsx +27 -0
- package/src/components/CalendarPage/Summary/{InsurancesAndRequired.js → InsurancesAndRequired.tsx} +21 -2
- package/src/components/CalendarPage/Summary/Object.tsx +59 -0
- package/src/components/CalendarPage/Summary/{OnSite.js → OnSite.tsx} +9 -9
- package/src/components/CalendarPage/Summary/{OptionalNotOnSite.js → OptionalNotOnSite.tsx} +19 -18
- package/src/components/CalendarPage/Summary/{OptionalOnSite.js → OptionalOnSite.tsx} +6 -1
- package/src/components/CalendarPage/Summary/{Queries.js → Queries.ts} +3 -3
- package/src/components/CalendarPage/Summary/RentAndDiscount.tsx +30 -0
- package/src/components/CalendarPage/Summary/{Totals.js → Totals.tsx} +8 -3
- package/src/components/CalendarPage/Summary/cost_types.d.ts +31 -0
- package/src/components/CalendarPage/Summary/index.tsx +24 -0
- package/src/components/CalendarPage/calender_types.d.ts +16 -0
- package/src/components/CalendarPage/formParts/AssistanceMessage.tsx +60 -0
- package/src/components/CalendarPage/formParts/{BookingHelpers.js → BookingHelpers.tsx} +3 -3
- package/src/components/CalendarPage/formParts/{BookingOrOption.js → BookingOrOption.tsx} +6 -1
- package/src/components/CalendarPage/formParts/CancelInsuranceText.tsx +105 -0
- package/src/components/CalendarPage/formParts/{DefaultBookingFields.js → DefaultBookingFields.ts} +3 -1
- package/src/components/CalendarPage/formParts/DiscountCode.tsx +62 -0
- package/src/components/CalendarPage/formParts/{Guests.js → Guests.tsx} +10 -4
- package/src/components/CalendarPage/formParts/{OptionalBookingFields.js → OptionalBookingFields.tsx} +0 -0
- package/src/components/CalendarPage/formParts/{OptionalCosts.js → OptionalCosts.tsx} +1 -2
- package/src/components/CalendarPage/formParts/{SuccessMessage.js → SuccessMessage.tsx} +0 -0
- package/src/components/CalendarPage/formParts/Validations.tsx +78 -0
- package/src/components/CalendarPage/formParts/{discount.js → discount.tsx} +11 -2
- package/src/components/CalendarPage/formParts/form_types.d.ts +38 -0
- package/src/components/CalendarPage/formParts/{insurances.js → insurances.tsx} +14 -10
- package/src/components/CalendarPage/formParts/{radioButtons.js → radioButtons.tsx} +0 -0
- package/src/components/Error/{ApiError.js → ApiError.tsx} +6 -4
- package/src/components/Error/{IntegrationError.js → IntegrationError.tsx} +17 -11
- package/src/components/Error/{index.js → index.ts} +0 -0
- package/src/components/{ErrorBoundary.js → ErrorBoundary.tsx} +13 -5
- package/src/components/Modal/index.tsx +46 -0
- package/src/components/ReviewsPage/Queries.ts +26 -0
- package/src/components/ReviewsPage/ReviewsPage.tsx +43 -0
- package/src/components/ReviewsPage/Score.tsx +25 -0
- package/src/components/ReviewsPage/SingleReview.tsx +38 -0
- package/src/components/SafeBooking.tsx +97 -0
- package/src/components/SearchPage/Field.tsx +75 -0
- package/src/components/SearchPage/Filters.tsx +91 -0
- package/src/components/SearchPage/Paginator.tsx +63 -0
- package/src/components/SearchPage/Results.tsx +129 -0
- package/src/components/SearchPage/{SearchPage.js → SearchPage.tsx} +42 -31
- package/src/components/SearchPage/{SingleResult.js → SingleResult.tsx} +15 -8
- package/src/components/SearchPage/filters/Categories.tsx +57 -0
- package/src/components/SearchPage/filters/DateFilter.tsx +34 -0
- package/src/components/SearchPage/filters/List.tsx +80 -0
- package/src/components/SearchPage/filters/NumberFilter.tsx +37 -0
- package/src/components/SearchPage/filters/Radio.tsx +46 -0
- package/src/components/SearchPage/filters/Select.tsx +85 -0
- package/src/components/SearchPage/filters/__tests__/helper.spec.js +15 -0
- package/src/components/SearchPage/filters/filter_types.d.ts +25 -0
- package/src/components/SearchPage/filters/helper.ts +19 -0
- package/src/components/icons/ArrowLeft.svg.tsx +20 -0
- package/src/components/icons/{ArrowRight.svg.js → ArrowRight.svg.tsx} +0 -0
- package/src/components/icons/{Reload.svg.js → Reload.svg.tsx} +0 -0
- package/src/components/icons/{info.svg.js → info.svg.tsx} +0 -0
- package/src/components/icons/{loading.svg.js → loading.svg.tsx} +1 -1
- package/src/custom.d.ts +10 -0
- package/src/index.tsx +93 -0
- package/src/locales/de.json +4 -3
- package/src/locales/en.json +4 -3
- package/src/locales/es.json +4 -3
- package/src/locales/fr.json +4 -3
- package/src/locales/it.json +4 -3
- package/src/locales/nl.json +4 -3
- package/src/styles/main.css +2 -1
- package/src/styles/modal.css +1 -1
- package/src/styles/pagination.css +25 -23
- package/src/styles/result.css +33 -2
- package/src/styles/reviews.css +76 -0
- package/src/types.d.ts +85 -0
- package/tsconfig.json +17 -0
- package/vite.config.ts +31 -0
- package/build/index.html +0 -16
- package/build/index.js +0 -48528
- package/cypress.json +0 -9
- package/rollup.config.js +0 -30
- package/src/_lib/format.js +0 -16
- package/src/components/App.js +0 -164
- package/src/components/CalendarPage/BookingForm.js +0 -57
- package/src/components/CalendarPage/Calendar.js +0 -373
- package/src/components/CalendarPage/CalendarHeader.js +0 -58
- package/src/components/CalendarPage/CalendarPage.js +0 -158
- package/src/components/CalendarPage/FormCreator.js +0 -278
- package/src/components/CalendarPage/FormItems/Wrapper.js +0 -0
- package/src/components/CalendarPage/PriceField.js +0 -203
- package/src/components/CalendarPage/Summary/Description.js +0 -22
- package/src/components/CalendarPage/Summary/Object.js +0 -46
- package/src/components/CalendarPage/Summary/RentAndDiscount.js +0 -21
- package/src/components/CalendarPage/Summary/index.js +0 -19
- package/src/components/CalendarPage/formParts/AssistanceMessage.js +0 -47
- package/src/components/CalendarPage/formParts/CancelInsuranceText.js +0 -91
- package/src/components/CalendarPage/formParts/DiscountCode.js +0 -62
- package/src/components/CalendarPage/formParts/summary.js +0 -43
- package/src/components/Modal/index.js +0 -58
- package/src/components/ReviewsPage/ReviewsPage.js +0 -15
- package/src/components/SafeBooking.js +0 -69
- package/src/components/SearchPage/Field.js +0 -241
- package/src/components/SearchPage/Filters.js +0 -108
- package/src/components/SearchPage/Paginator.js +0 -59
- package/src/components/SearchPage/Results.js +0 -130
- package/src/components/SearchPage/filters/List.js +0 -63
- package/src/components/icons/ArrowLeft.svg.js +0 -18
- package/src/index.js +0 -74
- package/webpack.config.dev.js +0 -53
- 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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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(
|
|
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
|
|
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,
|
|
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
|
-
:
|
|
67
|
-
:
|
|
85
|
+
: ''
|
|
86
|
+
: ''
|
|
68
87
|
}
|
|
69
88
|
>
|
|
70
89
|
<Filters
|
|
71
|
-
PortalSite={
|
|
90
|
+
PortalSite={PortalSite}
|
|
72
91
|
filters={filters}
|
|
73
92
|
onFilterChange={this.onFilterChange}
|
|
74
93
|
options={options}
|
|
75
94
|
/>
|
|
76
95
|
<Results
|
|
77
|
-
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
|
-
|
|
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
|
-
|
|
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
|