iceholidays-frontend 0.6.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/iceholidays/frontend/_slick-theme.scss +194 -0
- data/app/assets/stylesheets/iceholidays/frontend/_slick.scss +100 -0
- data/app/assets/stylesheets/iceholidays/frontend/application.sass.scss +56 -3
- data/app/assets/stylesheets/iceholidays/frontend/common.scss +14 -0
- data/app/assets/stylesheets/iceholidays/frontend/layout.scss +11 -2
- data/app/assets/stylesheets/iceholidays/frontend/utils/_slick_overrides.scss +3 -0
- data/app/javascript/api-services/brochure-api.service.ts +17 -0
- data/app/javascript/api-services/contact-us-api.service.ts +19 -0
- data/app/javascript/api-services/posts-api.service.ts +47 -0
- data/app/javascript/api-services/testimonials-api.service.ts +1 -1
- data/app/javascript/interfaces/blog.interface.ts +8 -0
- data/app/javascript/interfaces/testimonial.interface.ts +1 -1
- data/app/javascript/react/App.tsx +1 -1
- data/app/javascript/react/components/Destinations.tsx +21 -10
- data/app/javascript/react/components/Testimonials.tsx +5 -2
- data/app/javascript/react/components/shared/LocationDropdown.tsx +3 -1
- data/app/javascript/react/index.js +2 -1
- data/app/javascript/react/layouts/MainFooter.tsx +43 -35
- data/app/javascript/react/layouts/MainHeader.tsx +80 -32
- data/app/javascript/react/layouts/MainLayout.tsx +1 -1
- data/app/javascript/react/pages/BlogPage.tsx +82 -66
- data/app/javascript/react/pages/BlogShowPage.tsx +50 -35
- data/app/javascript/react/pages/ContactUsPage.tsx +128 -101
- data/app/javascript/react/pages/ListingPage.tsx +42 -18
- data/app/javascript/react/pages/ShowPage.tsx +84 -52
- data/app/javascript/react/widgets/FilterPills.tsx +21 -18
- data/app/views/iceholidays/frontend/site/index.html.erb +7 -1
- data/config/routes.rb +1 -1
- data/lib/iceholidays/frontend/version.rb +1 -1
- data/public/iceholidays-assets/application.css +304 -9
- data/public/iceholidays-assets/application.js +161 -121
- data/public/iceholidays-assets/application.js.map +4 -4
- data/public/iceholidays-assets/images/TST Ribbon@2x.png +0 -0
- data/public/iceholidays-assets/images/TST Ribbon@3x.png +0 -0
- metadata +11 -2
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import Headline from "../components/shared/Headline";
|
|
3
|
-
import { Button, Col, Flex, Form, Input, Row, Select, Space } from "antd";
|
|
3
|
+
import { Button, Col, Flex, Form, Input, notification, Row, Select, Space } from "antd";
|
|
4
4
|
import { mdiFacebook, mdiInstagram, mdiTwitter, mdiYoutube } from "@mdi/js";
|
|
5
5
|
import Icon from "@mdi/react";
|
|
6
|
+
import ContactUsApi from "../../api-services/contact-us-api.service";
|
|
6
7
|
const { TextArea } = Input;
|
|
7
8
|
|
|
8
9
|
const bannerPath = '/iceholidays-assets/images/contact_us.png';
|
|
@@ -11,112 +12,138 @@ const breadcrumbs = [
|
|
|
11
12
|
{ title: 'Contact Us' }
|
|
12
13
|
]
|
|
13
14
|
|
|
14
|
-
const ContactUsPage = React.FC = () => {
|
|
15
|
-
const [form] = Form.useForm();
|
|
16
|
-
|
|
17
|
-
return (
|
|
18
|
-
<div id="contact-us-page">
|
|
19
|
-
<Headline bannerImage={bannerPath} breadcrumbs={breadcrumbs} title="contact us"/>
|
|
20
|
-
|
|
21
|
-
<div id="contact-us-page_body">
|
|
22
|
-
<img src="/iceholidays-assets/images/contact_us_form.png"/>
|
|
23
|
-
|
|
24
|
-
<div id="contact-us-form">
|
|
25
|
-
<div id="contact-us-form_header">
|
|
26
|
-
<h2>Get in touch</h2>
|
|
27
|
-
<span>Send us your enquiry</span>
|
|
28
|
-
</div>
|
|
29
|
-
<Form
|
|
30
|
-
form={form}
|
|
31
|
-
autoComplete="off"
|
|
32
|
-
layout="vertical"
|
|
33
|
-
variant="filled"
|
|
34
|
-
>
|
|
35
|
-
<Form.Item label="Enter Name" name="name">
|
|
36
|
-
<Input placeholder="Enter Here" />
|
|
37
|
-
</Form.Item>
|
|
38
15
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
</Col>
|
|
50
|
-
</Row>
|
|
16
|
+
const validateMessages = {
|
|
17
|
+
required: '${label} is required!',
|
|
18
|
+
types: {
|
|
19
|
+
email: '${label} is not a valid email!',
|
|
20
|
+
number: '${label} is not a valid number!',
|
|
21
|
+
},
|
|
22
|
+
number: {
|
|
23
|
+
range: '${label} must be between ${min} and ${max}',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
51
26
|
|
|
52
|
-
<Form.Item label="Select State" name="state">
|
|
53
|
-
<Select
|
|
54
|
-
placeholder="Please Select"
|
|
55
|
-
defaultValue="lucy"
|
|
56
|
-
options={[
|
|
57
|
-
{ value: 'jack', label: 'Jack' },
|
|
58
|
-
]}
|
|
59
|
-
/>
|
|
60
|
-
</Form.Item>
|
|
61
27
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
placeholder="Please Select"
|
|
65
|
-
defaultValue="lucy"
|
|
66
|
-
options={[
|
|
67
|
-
{ value: 'jack', label: 'Jack' },
|
|
68
|
-
]}
|
|
69
|
-
/>
|
|
70
|
-
</Form.Item>
|
|
28
|
+
export default class ContactUsPage extends React.Component {
|
|
29
|
+
api = new ContactUsApi;
|
|
71
30
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
defaultValue="lucy"
|
|
76
|
-
options={[
|
|
77
|
-
{ value: 'jack', label: 'Jack' },
|
|
78
|
-
]}
|
|
79
|
-
/>
|
|
80
|
-
</Form.Item>
|
|
31
|
+
state = {
|
|
32
|
+
states: []
|
|
33
|
+
}
|
|
81
34
|
|
|
82
|
-
<Form.Item label="Write your message" name="message">
|
|
83
|
-
<TextArea rows={4} placeholder="Write Here"/>
|
|
84
|
-
</Form.Item>
|
|
85
35
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
36
|
+
componentDidMount() {
|
|
37
|
+
this.api.getStates()
|
|
38
|
+
.then(statesData => {
|
|
39
|
+
this.setState({states: statesData})
|
|
40
|
+
})
|
|
41
|
+
.catch(error => {
|
|
42
|
+
notification.error({ message: 'An error occured while loading states.'});
|
|
43
|
+
});
|
|
44
|
+
}
|
|
91
45
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
46
|
+
submitForm(formValues) {
|
|
47
|
+
console.log(formValues)
|
|
48
|
+
}
|
|
95
49
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<
|
|
105
|
-
<div>
|
|
106
|
-
<
|
|
107
|
-
<
|
|
108
|
-
<Icon path={mdiFacebook} size={1} />
|
|
109
|
-
<Icon path={mdiInstagram} size={1} />
|
|
110
|
-
<Icon path={mdiTwitter} size={1} />
|
|
111
|
-
<Icon path={mdiYoutube} size={1} />
|
|
112
|
-
<img src="/iceholidays-assets/images/social.png" className="social-icon"/>
|
|
113
|
-
</Space>
|
|
50
|
+
render(){
|
|
51
|
+
return (
|
|
52
|
+
<div id="contact-us-page">
|
|
53
|
+
<Headline bannerImage={bannerPath} breadcrumbs={breadcrumbs} title="contact us"/>
|
|
54
|
+
|
|
55
|
+
<div id="contact-us-page_body">
|
|
56
|
+
<img src="/iceholidays-assets/images/contact_us_form.png"/>
|
|
57
|
+
|
|
58
|
+
<div id="contact-us-form">
|
|
59
|
+
<div id="contact-us-form_header">
|
|
60
|
+
<h2>Get in touch</h2>
|
|
61
|
+
<span>Send us your enquiry</span>
|
|
114
62
|
</div>
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
63
|
+
<Form
|
|
64
|
+
autoComplete="off"
|
|
65
|
+
layout="vertical"
|
|
66
|
+
variant="filled"
|
|
67
|
+
validateMessages={validateMessages}
|
|
68
|
+
onFinish={(formValues)=>this.submitForm(formValues)}
|
|
69
|
+
>
|
|
70
|
+
<Form.Item label="Enter Name" name="name">
|
|
71
|
+
<Input placeholder="Enter Here" />
|
|
72
|
+
</Form.Item>
|
|
73
|
+
|
|
74
|
+
<Row gutter={20}>
|
|
75
|
+
<Col span={12}>
|
|
76
|
+
<Form.Item label="Enter Email" name="email" rules={[{ type: 'email' }]}>
|
|
77
|
+
<Input placeholder="Enter Here" />
|
|
78
|
+
</Form.Item>
|
|
79
|
+
</Col>
|
|
80
|
+
<Col span={12}>
|
|
81
|
+
<Form.Item label="Enter Phone Number" name="phoneNumber">
|
|
82
|
+
<Input placeholder="Enter Here" />
|
|
83
|
+
</Form.Item>
|
|
84
|
+
</Col>
|
|
85
|
+
</Row>
|
|
86
|
+
|
|
87
|
+
<Form.Item label="Select State" name="state">
|
|
88
|
+
<Select
|
|
89
|
+
placeholder="Please Select"
|
|
90
|
+
options={this.state?.states}
|
|
91
|
+
/>
|
|
92
|
+
</Form.Item>
|
|
93
|
+
|
|
94
|
+
<Form.Item label="Select Area" name="area">
|
|
95
|
+
<Input placeholder="Enter Here" />
|
|
96
|
+
</Form.Item>
|
|
97
|
+
|
|
98
|
+
<Form.Item label="Select Subject" name="subject">
|
|
99
|
+
<Input placeholder="Enter Here" />
|
|
100
|
+
</Form.Item>
|
|
101
|
+
|
|
102
|
+
<Form.Item label="Write your message" name="message">
|
|
103
|
+
<TextArea rows={4} placeholder="Write Here"/>
|
|
104
|
+
</Form.Item>
|
|
105
|
+
|
|
106
|
+
<Flex align="center" justify="center">
|
|
107
|
+
<Button type="primary" htmlType="submit">
|
|
108
|
+
Submit
|
|
109
|
+
</Button>
|
|
110
|
+
</Flex>
|
|
111
|
+
|
|
112
|
+
</Form>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
<div id="contact-us-page_footer">
|
|
117
|
+
<Row gutter={[29, 20]} >
|
|
118
|
+
<Col xs={24} sm={24} lg={12}>
|
|
119
|
+
<div>
|
|
120
|
+
<label>Contact Us</label>
|
|
121
|
+
<span>feedback@gd.my</span>
|
|
122
|
+
</div>
|
|
123
|
+
</Col>
|
|
124
|
+
<Col xs={24} sm={24} lg={12}>
|
|
125
|
+
<div>
|
|
126
|
+
<label>Connect with Us</label>
|
|
127
|
+
<Space size={29}>
|
|
128
|
+
<a target="_blank" href="https://www.facebook.com/thesignaturetour">
|
|
129
|
+
<Icon path={mdiFacebook} size={1} />
|
|
130
|
+
</a>
|
|
131
|
+
<a target="_blank" href="https://www.instagram.com/thesignaturetours">
|
|
132
|
+
<Icon path={mdiInstagram} size={1} />
|
|
133
|
+
</a>
|
|
134
|
+
<a target="_blank" href="javascript:void(0)" style={{pointerEvents: "none"}}>
|
|
135
|
+
<Icon path={mdiTwitter} size={1} />
|
|
136
|
+
</a>
|
|
137
|
+
<a target="_blank" href="javascript:void(0)" style={{pointerEvents: "none"}}>
|
|
138
|
+
<Icon path={mdiYoutube} size={1} />
|
|
139
|
+
</a>
|
|
140
|
+
<img src="/iceholidays-assets/images/social.png" className="social-icon"/>
|
|
141
|
+
</Space>
|
|
142
|
+
</div>
|
|
143
|
+
</Col>
|
|
144
|
+
</Row>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -12,6 +12,11 @@ import { Itinerary } from "../../interfaces/itinerary.interface";
|
|
|
12
12
|
|
|
13
13
|
import createDOMPurify from 'dompurify'
|
|
14
14
|
import LocationDropdown from "../components/shared/LocationDropdown";
|
|
15
|
+
import ReactMarkdown from "react-markdown";
|
|
16
|
+
import Markdown from "react-markdown";
|
|
17
|
+
import remarkGfm from "remark-gfm";
|
|
18
|
+
import rehypeRaw from "rehype-raw";
|
|
19
|
+
import rehypeSanitize from "rehype-sanitize";
|
|
15
20
|
|
|
16
21
|
const DOMPurify = createDOMPurify(window)
|
|
17
22
|
|
|
@@ -38,7 +43,7 @@ class ListingPage extends React.Component <{searchParams}> {
|
|
|
38
43
|
|
|
39
44
|
state = {
|
|
40
45
|
countries: [],
|
|
41
|
-
selectedCountry: {cities: []},
|
|
46
|
+
selectedCountry: {name: "", cities: []},
|
|
42
47
|
searchParamsObj: {keyword: "", year: "", month: "", location_id: null},
|
|
43
48
|
itineraries: [],
|
|
44
49
|
location: {name: "", cover: ""},
|
|
@@ -56,15 +61,22 @@ class ListingPage extends React.Component <{searchParams}> {
|
|
|
56
61
|
}
|
|
57
62
|
|
|
58
63
|
componentDidMount() {
|
|
64
|
+
this.setSearchParamsObj();
|
|
65
|
+
|
|
59
66
|
this.locationsApi.getCountries()
|
|
60
67
|
.then(locationsData => {
|
|
61
|
-
|
|
68
|
+
var selectedCountry = locationsData[0];
|
|
69
|
+
const searchParams = this.state.searchParamsObj;
|
|
70
|
+
if(searchParams.location_id){
|
|
71
|
+
const country = locationsData.find(loc => loc.id == searchParams.location_id);
|
|
72
|
+
if(country) selectedCountry = country;
|
|
73
|
+
}
|
|
74
|
+
this.setState({countries: locationsData, selectedCountry})
|
|
75
|
+
this.getItineraries(searchParams, true);
|
|
62
76
|
})
|
|
63
77
|
.catch(error => {
|
|
64
78
|
notification.error({ message: 'An error occured while loading countries.'});
|
|
65
79
|
});
|
|
66
|
-
|
|
67
|
-
this.setSearchParamsObj();
|
|
68
80
|
|
|
69
81
|
}
|
|
70
82
|
|
|
@@ -74,19 +86,18 @@ class ListingPage extends React.Component <{searchParams}> {
|
|
|
74
86
|
searchParams.keys().forEach(key => {
|
|
75
87
|
searchParamsObj[key] = searchParams.get(key);
|
|
76
88
|
});
|
|
77
|
-
|
|
78
|
-
this.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if(locationId) this.getLocation(locationId);
|
|
82
|
-
|
|
83
|
-
this.getItineraries(searchParamsObj);
|
|
89
|
+
|
|
90
|
+
const {breadcrumbs} = this.state;
|
|
91
|
+
breadcrumbs[1] = {title: this.searchParamsToText()};
|
|
92
|
+
this.setState({searchParamsObj, breadcrumbs});
|
|
84
93
|
}
|
|
85
94
|
|
|
86
95
|
|
|
87
96
|
searchParamsToText() {
|
|
88
97
|
var {keyword, year, month} = this.state.searchParamsObj;
|
|
89
|
-
|
|
98
|
+
const monthYear = (month || year) && `${month} ${year}`;
|
|
99
|
+
const text = [(this.state.location?.name || keyword), monthYear].filter(val => val).join(", ");
|
|
100
|
+
return <span>{ text } </span>
|
|
90
101
|
}
|
|
91
102
|
|
|
92
103
|
getLocation(locationId){
|
|
@@ -109,7 +120,17 @@ class ListingPage extends React.Component <{searchParams}> {
|
|
|
109
120
|
});
|
|
110
121
|
}
|
|
111
122
|
|
|
112
|
-
getItineraries(searchParams){
|
|
123
|
+
getItineraries(searchParams, init = false){
|
|
124
|
+
const locationId = searchParams.location_id;
|
|
125
|
+
const storedSearchParamsStr = localStorage.getItem("searchParams");
|
|
126
|
+
if(storedSearchParamsStr != null){
|
|
127
|
+
const storedSearchParams = JSON.parse(storedSearchParamsStr);
|
|
128
|
+
const storedLocationId = storedSearchParams.location_id;
|
|
129
|
+
if(locationId != storedLocationId || (init && locationId)) this.getLocation(locationId);
|
|
130
|
+
}else{
|
|
131
|
+
if(locationId) this.getLocation(locationId);
|
|
132
|
+
}
|
|
133
|
+
|
|
113
134
|
this.seriesApi.getItineraries(searchParams)
|
|
114
135
|
.then(itinerariesData => {
|
|
115
136
|
this.setBreadcrumbs();
|
|
@@ -118,13 +139,16 @@ class ListingPage extends React.Component <{searchParams}> {
|
|
|
118
139
|
.catch(error => {
|
|
119
140
|
notification.error({ message: 'An error occured while loading itineraries.'});
|
|
120
141
|
});
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
localStorage.setItem("searchParams", JSON.stringify(searchParams));
|
|
121
145
|
}
|
|
122
146
|
|
|
123
147
|
setBreadcrumbs(){
|
|
124
148
|
var breadcrumbs = this.state.breadcrumbs;
|
|
125
149
|
if(breadcrumbs.length > 1){
|
|
126
150
|
const {keyword, ...noKeyword} = this.state.searchParamsObj;
|
|
127
|
-
const allHasValues = Object.entries(noKeyword).every(o => o[1] != "");
|
|
151
|
+
const allHasValues = Object.entries(noKeyword).every(o => o[1] != "" && o[1] != null);
|
|
128
152
|
if(allHasValues){
|
|
129
153
|
const searchResultsCrumb = {title: <span>Search results</span>};
|
|
130
154
|
if(breadcrumbs.length > 2){
|
|
@@ -153,7 +177,7 @@ class ListingPage extends React.Component <{searchParams}> {
|
|
|
153
177
|
if(selectedCountry){
|
|
154
178
|
const searchParamsObj = this.state.searchParamsObj;
|
|
155
179
|
searchParamsObj.location_id = selectedCountry.id;
|
|
156
|
-
this.getLocation(selectedCountry.id);
|
|
180
|
+
// this.getLocation(selectedCountry.id);
|
|
157
181
|
|
|
158
182
|
this.setState({selectedCountry, searchParamsObj});
|
|
159
183
|
this.getItineraries(searchParamsObj);
|
|
@@ -170,7 +194,7 @@ class ListingPage extends React.Component <{searchParams}> {
|
|
|
170
194
|
<div id="listing-page_header">
|
|
171
195
|
<Flex vertical gap={10}>
|
|
172
196
|
<h1>{this.searchParamsToText()}</h1>
|
|
173
|
-
<LocationDropdown locations={countries} selectLocation={this.selectCountry}/>
|
|
197
|
+
<LocationDropdown initialValue={selectedCountry.name} locations={countries} selectLocation={this.selectCountry}/>
|
|
174
198
|
</Flex>
|
|
175
199
|
</div>
|
|
176
200
|
</Headline>
|
|
@@ -254,13 +278,13 @@ class ListingPage extends React.Component <{searchParams}> {
|
|
|
254
278
|
</Flex>
|
|
255
279
|
</div>
|
|
256
280
|
</>
|
|
257
|
-
) : <h1 id="no-tours-found">
|
|
281
|
+
) : <h1 id="no-tours-found">No tour package is found.</h1>
|
|
258
282
|
}
|
|
259
283
|
|
|
260
284
|
</div>
|
|
261
285
|
|
|
262
286
|
<Modal title="Description" open={this.state.descriptionModalOpen} onCancel={()=>this.closeModal("descriptionModalOpen")} footer={null} width={1000} centered className="tour_details_description">
|
|
263
|
-
<
|
|
287
|
+
<Markdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>{descriptionData}</Markdown>
|
|
264
288
|
</Modal>
|
|
265
289
|
|
|
266
290
|
<Modal title="Itinerary" open={this.state.itineraryModalOpen} onCancel={()=>this.closeModal("itineraryModalOpen")} footer={null} width={1000} centered className="tour_details_itinerary">
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import React
|
|
2
|
-
import { Button,
|
|
3
|
-
import SlickButtonFix from "../components/shared/SlickButtonFix";
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Button, Col, Flex, Layout, Modal, notification, Row, Skeleton, Space } from "antd";
|
|
4
3
|
import { mdiAccountCash, mdiAccountGroup, mdiAirplane, mdiBagChecked, mdiBedKing, mdiClose, mdiEmailOutline, mdiFileDownload, mdiFlagTriangle, mdiMapMarker, mdiMapMarkerOutline, mdiMenuDown, mdiMenuLeft, mdiMenuRight, mdiMenuUp, mdiPhoneInTalkOutline, mdiReceiptText, mdiShieldAccount, mdiShieldAirplane, mdiSilverwareForkKnife, mdiWhatsapp } from "@mdi/js";
|
|
5
4
|
import Icon from "@mdi/react";
|
|
6
5
|
import Headline from "../components/shared/Headline";
|
|
@@ -13,11 +12,48 @@ import AgentsApi from "../../api-services/agents-api.service";
|
|
|
13
12
|
import { Agent } from "../../interfaces/agent.interface";
|
|
14
13
|
import PriceDetails from "../components/PriceDetails";
|
|
15
14
|
import ContactAgentsForm from "../components/shared/ContactAgentsForm";
|
|
15
|
+
import Markdown from "react-markdown";
|
|
16
|
+
import rehypeRaw from "rehype-raw";
|
|
17
|
+
import remarkGfm from "remark-gfm";
|
|
18
|
+
import Slider from "react-slick";
|
|
16
19
|
|
|
17
20
|
const breadcrumbs = [
|
|
18
21
|
{ title: 'Home' },
|
|
19
22
|
]
|
|
20
23
|
|
|
24
|
+
|
|
25
|
+
const settings = {
|
|
26
|
+
dots: false,
|
|
27
|
+
arrows: true,
|
|
28
|
+
slidesToShow: 7.5,
|
|
29
|
+
pauseOnHover:false,
|
|
30
|
+
speed: 500,
|
|
31
|
+
infinite: false,
|
|
32
|
+
slidesToScroll: 1,
|
|
33
|
+
prevArrow: <Icon path={mdiMenuLeft} size={2} />,
|
|
34
|
+
nextArrow: <Icon path={mdiMenuRight} size={2} />,
|
|
35
|
+
responsive: [
|
|
36
|
+
{
|
|
37
|
+
breakpoint: 1024,
|
|
38
|
+
settings: {
|
|
39
|
+
slidesToShow: 5.5,
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
breakpoint: 768,
|
|
44
|
+
settings: {
|
|
45
|
+
slidesToShow: 3.5,
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
breakpoint: 480,
|
|
50
|
+
settings: {
|
|
51
|
+
slidesToShow: 2.5,
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
]
|
|
55
|
+
};
|
|
56
|
+
|
|
21
57
|
const thingsToKnow = (inclusionsData) => {
|
|
22
58
|
return [
|
|
23
59
|
{icon: <Icon path={mdiShieldAccount} size={1} />, label: "Free Travel Insurance", visible: inclusionsData.acf},
|
|
@@ -171,6 +207,7 @@ class ShowPage extends React.Component <{params;}> {
|
|
|
171
207
|
const dates = itinerary.tours.length == 0 ? [] : itinerary.tours.map((tour:any) => {
|
|
172
208
|
return {id: tour.id, date: tour.departure_date, price: `${itinerary.priceCurrency} ${tour.price}`}
|
|
173
209
|
});
|
|
210
|
+
const tourInclusives = thingsToKnow(itinerary.includings);
|
|
174
211
|
|
|
175
212
|
return (
|
|
176
213
|
<div id="show-page">
|
|
@@ -191,31 +228,21 @@ class ShowPage extends React.Component <{params;}> {
|
|
|
191
228
|
</Headline>
|
|
192
229
|
|
|
193
230
|
<div id="date-selector">
|
|
194
|
-
<
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
<
|
|
198
|
-
|
|
231
|
+
<Slider {...settings}>
|
|
232
|
+
{
|
|
233
|
+
dates.map((tour, index) => {
|
|
234
|
+
return <div key={index} className={`date-box ${selectedTour?.id == tour.id && "selected"}`} onClick={()=>this.selectTour(tour.id)}>
|
|
235
|
+
<Flex justify="space-between" align="center" vertical gap={8}>
|
|
236
|
+
<div className="date-box_date"> {tour.date} </div>
|
|
237
|
+
<div>
|
|
238
|
+
<span>From</span>
|
|
239
|
+
<div className="date-box_price"> {tour.price} </div>
|
|
240
|
+
</div>
|
|
241
|
+
</Flex>
|
|
242
|
+
</div>
|
|
243
|
+
})
|
|
199
244
|
}
|
|
200
|
-
|
|
201
|
-
<SlickButtonFix>
|
|
202
|
-
<Icon path={mdiMenuRight} size={2} />
|
|
203
|
-
</SlickButtonFix>
|
|
204
|
-
}>
|
|
205
|
-
{
|
|
206
|
-
dates.map((tour, index) => {
|
|
207
|
-
return <div key={index} className={`date-box ${selectedTour?.id == tour.id && "selected"}`} onClick={()=>this.selectTour(tour.id)}>
|
|
208
|
-
<Flex justify="space-between" align="center" vertical gap={8}>
|
|
209
|
-
<div className="date-box_date"> {tour.date} </div>
|
|
210
|
-
<div>
|
|
211
|
-
<span>From</span>
|
|
212
|
-
<div className="date-box_price"> {tour.price} </div>
|
|
213
|
-
</div>
|
|
214
|
-
</Flex>
|
|
215
|
-
</div>
|
|
216
|
-
})
|
|
217
|
-
}
|
|
218
|
-
</Carousel>
|
|
245
|
+
</Slider>
|
|
219
246
|
</div>
|
|
220
247
|
</>
|
|
221
248
|
|
|
@@ -231,36 +258,41 @@ class ShowPage extends React.Component <{params;}> {
|
|
|
231
258
|
) : (
|
|
232
259
|
<div className="details">
|
|
233
260
|
<Flex gap={10} vertical>
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
261
|
+
{
|
|
262
|
+
(tourInclusives.every(x => x.visible) || selectedTour?.guaranteed_departure) && (
|
|
263
|
+
<div className="details-container">
|
|
264
|
+
<div className="details-container_header"> Tour Inclusive </div>
|
|
265
|
+
<div className="details-container_content">
|
|
266
|
+
<div id="things-to-know">
|
|
267
|
+
{
|
|
268
|
+
tourInclusives.filter(item => item.visible)
|
|
269
|
+
.map(item => (
|
|
270
|
+
<div className="item">
|
|
271
|
+
<div className="icon"> {item.icon} </div>
|
|
272
|
+
<label>{item.label}</label>
|
|
273
|
+
</div>
|
|
274
|
+
))
|
|
275
|
+
}
|
|
276
|
+
{
|
|
277
|
+
selectedTour?.guaranteed_departure && (
|
|
278
|
+
<div className="item guaranteed">
|
|
279
|
+
<div className="icon"> <Icon path={mdiShieldAirplane} size={1} /></div>
|
|
280
|
+
<label>Guaranteed Departure</label>
|
|
281
|
+
</div>
|
|
282
|
+
)
|
|
283
|
+
}
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
256
286
|
</div>
|
|
257
|
-
|
|
258
|
-
|
|
287
|
+
)
|
|
288
|
+
}
|
|
259
289
|
|
|
260
290
|
<div className="details-container">
|
|
261
291
|
<div className="details-container_header"> Description </div>
|
|
262
292
|
<div className="details-container_content">
|
|
263
|
-
<div id="description" className={
|
|
293
|
+
<div id="description" className={`${isCollapsed && 'collapsed'}`}>
|
|
294
|
+
<Markdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>{itinerary?.description}</Markdown>
|
|
295
|
+
</div>
|
|
264
296
|
<Button className="toggle-description" color="default" variant="filled" block onClick={this.toggleDescription}>{ isCollapsed ? 'Expand' : 'Collapse'}
|
|
265
297
|
<Icon path={isCollapsed ? mdiMenuDown : mdiMenuUp} size="19px" />
|
|
266
298
|
</Button>
|
|
@@ -19,10 +19,10 @@ function FilterPills(
|
|
|
19
19
|
props: {
|
|
20
20
|
items:any[];
|
|
21
21
|
title?:string;
|
|
22
|
-
initialValue:{keyword?:string, year?:
|
|
23
|
-
bindLabel
|
|
24
|
-
bindValue
|
|
25
|
-
allOption
|
|
22
|
+
initialValue:{keyword?:string, year?:any, month?: string, location_id?:any, type?: any}
|
|
23
|
+
bindLabel:string;
|
|
24
|
+
bindValue:string;
|
|
25
|
+
allOption:boolean;
|
|
26
26
|
selectFilter;
|
|
27
27
|
}){
|
|
28
28
|
|
|
@@ -32,7 +32,7 @@ function FilterPills(
|
|
|
32
32
|
|
|
33
33
|
const allHasValues = () => {
|
|
34
34
|
const {keyword, ...noKeyword} = selected;
|
|
35
|
-
return Object.entries(noKeyword).every(o => o[1] != "");
|
|
35
|
+
return Object.entries(noKeyword).every(o => o[1] != "" && o[1] != null);
|
|
36
36
|
}
|
|
37
37
|
const [collapseFilters, setcollapseFilters] = useState(allHasValues);
|
|
38
38
|
|
|
@@ -57,16 +57,15 @@ function FilterPills(
|
|
|
57
57
|
{ title && <div className="filter-title"> {title} </div> }
|
|
58
58
|
<Space size={10} wrap>
|
|
59
59
|
{
|
|
60
|
-
allOption && <div className=
|
|
60
|
+
allOption && <div onClick={()=>selectFilter(bindValue, null)} className={`filter-pill ${(selected[bindValue] == null || selected[bindValue] == '') && 'selected'}`}> <span> All </span></div>
|
|
61
61
|
}
|
|
62
62
|
{
|
|
63
63
|
items.map((item, index) => {
|
|
64
64
|
//special case for location id
|
|
65
65
|
const id:any = bindValue == "location_id" ? "id" : bindValue;
|
|
66
|
-
const label = bindLabel ? item[bindLabel] : item;
|
|
67
66
|
const value = bindValue ? item[id] : item;
|
|
68
67
|
const selectedItem = selected && bindValue ? selected[bindValue]: item ;
|
|
69
|
-
return <div key={index} onClick={()=>selectFilter(bindValue, value)} className={`filter-pill ${selectedItem == value && 'selected'}`}> <span>{
|
|
68
|
+
return <div key={index} onClick={()=>selectFilter(bindValue, value)} className={`filter-pill ${selectedItem == value && 'selected'}`}> <span>{ item[bindLabel] } </span></div>
|
|
70
69
|
})
|
|
71
70
|
}
|
|
72
71
|
</Space>
|
|
@@ -77,26 +76,24 @@ function FilterPills(
|
|
|
77
76
|
<div className="filter-title"> Year </div>
|
|
78
77
|
<Space size={10} wrap>
|
|
79
78
|
{
|
|
80
|
-
allOption && <div className=
|
|
79
|
+
allOption && <div onClick={()=>selectFilter("year", "")} className={`filter-pill ${selected.year == '' && 'selected'}`}> <span> All </span></div>
|
|
81
80
|
}
|
|
82
81
|
{
|
|
83
|
-
years().map((year, index) =>
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
})
|
|
82
|
+
years().map((year, index) => (
|
|
83
|
+
<div key={index} onClick={()=>selectFilter("year", year)} className={`filter-pill default-filter ${selected.year == year && 'selected'}`}> <span>{year} </span></div>
|
|
84
|
+
))
|
|
87
85
|
}
|
|
88
86
|
</Space>
|
|
89
87
|
|
|
90
88
|
<div className="filter-title"> Month </div>
|
|
91
89
|
<Row gutter={[10, 10]} justify="space-between">
|
|
92
90
|
{
|
|
93
|
-
allOption && <
|
|
91
|
+
allOption && <div onClick={()=>selectFilter("month", "")} className={`filter-pill ${selected.month == '' && 'selected'}`}> <span> All </span></div>
|
|
94
92
|
}
|
|
95
93
|
{
|
|
96
|
-
months.map((month, index) =>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
})
|
|
94
|
+
months.map((month, index) => (
|
|
95
|
+
<Col key={index} xs={8} md={6} lg={4}> <div onClick={()=>selectFilter("month", month.shortMonth)} className={`filter-pill default-filter ${selected.month == month.shortMonth && 'selected'}`}> <span>{month.name} </span></div> </Col>
|
|
96
|
+
))
|
|
100
97
|
}
|
|
101
98
|
</Row>
|
|
102
99
|
</Space>
|
|
@@ -108,4 +105,10 @@ function FilterPills(
|
|
|
108
105
|
|
|
109
106
|
}
|
|
110
107
|
|
|
108
|
+
FilterPills.defaultProps = {
|
|
109
|
+
allOption: false,
|
|
110
|
+
bindLabel: "label",
|
|
111
|
+
bindValue: "id"
|
|
112
|
+
}
|
|
113
|
+
|
|
111
114
|
export default FilterPills;
|