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.
- 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,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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
};
|
|
File without changes
|
|
@@ -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(
|
|
7
|
-
|
|
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
|
-
|
|
4
|
-
|
|
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(
|
|
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}, </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;
|