iceholidays-frontend 0.5.0 → 0.7.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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/iceholidays/frontend/_slick-theme.scss +194 -0
  3. data/app/assets/stylesheets/iceholidays/frontend/_slick.scss +100 -0
  4. data/app/assets/stylesheets/iceholidays/frontend/application.sass.scss +64 -4
  5. data/app/assets/stylesheets/iceholidays/frontend/common.scss +17 -2
  6. data/app/assets/stylesheets/iceholidays/frontend/layout.scss +1 -1
  7. data/app/assets/stylesheets/iceholidays/frontend/utils/_slick_overrides.scss +3 -0
  8. data/app/javascript/api-services/brochure-api.service.ts +17 -0
  9. data/app/javascript/api-services/posts-api.service.ts +47 -0
  10. data/app/javascript/interfaces/blog.interface.ts +8 -0
  11. data/app/javascript/react/App.tsx +1 -1
  12. data/app/javascript/react/components/Destinations.tsx +21 -10
  13. data/app/javascript/react/components/PriceDetails.tsx +6 -6
  14. data/app/javascript/react/components/Testimonials.tsx +4 -1
  15. data/app/javascript/react/components/shared/LocationDropdown.tsx +3 -1
  16. data/app/javascript/react/layouts/MainHeader.tsx +80 -32
  17. data/app/javascript/react/pages/AboutUsPage.tsx +4 -4
  18. data/app/javascript/react/pages/BlogPage.tsx +82 -66
  19. data/app/javascript/react/pages/BlogShowPage.tsx +50 -35
  20. data/app/javascript/react/pages/ListingPage.tsx +115 -85
  21. data/app/javascript/react/pages/ShowPage.tsx +124 -87
  22. data/app/javascript/react/widgets/FilterPills.tsx +21 -18
  23. data/config/routes.rb +1 -1
  24. data/lib/iceholidays/frontend/version.rb +1 -1
  25. data/public/iceholidays-assets/application.css +306 -11
  26. data/public/iceholidays-assets/application.js +161 -121
  27. data/public/iceholidays-assets/application.js.map +4 -4
  28. data/public/iceholidays-assets/images/TST Ribbon@2x.png +0 -0
  29. data/public/iceholidays-assets/images/TST Ribbon@3x.png +0 -0
  30. data/public/iceholidays-assets/images/TSTRibbon.png +0 -0
  31. metadata +11 -2
@@ -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
- this.setState({countries: locationsData, selectedCountry: locationsData[0]})
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.setState({searchParamsObj});
79
-
80
- const locationId = searchParamsObj.location_id;
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
- return <span>{ this.state.location?.name || keyword}{`${month && `, ${month}`} ${year}`} </span>
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>
@@ -180,81 +204,87 @@ class ListingPage extends React.Component <{searchParams}> {
180
204
  <FilterPills items={selectedCountry.cities} bindLabel="name" bindValue="location_id" initialValue={searchParamsObj} selectFilter={(selected) => this.getItineraries(selected)}></FilterPills>
181
205
  </Space>
182
206
 
183
- <div id="legends">
184
- <Space size={17}>
185
- {
186
- legends.map(legend => <Badge key={legend.id} color={legend.color} text={legend.label} />)
187
- }
188
- </Space>
189
- </div>
190
-
191
- <div id="tours">
192
- <Flex vertical gap={30}>
193
- {
194
- itineraries.map((itinerary:Itinerary) => {
195
- const departureDates = itinerary.departureDate ? itinerary.departureDate.map(ddate => {
196
- var type = (itinerary.guranteedDepartureDates && itinerary.guranteedDepartureDates.includes(ddate)) ? "guaranteed" :
197
- (itinerary.almostGuaranteedDepartureDates && itinerary.almostGuaranteedDepartureDates.includes(ddate)) ? "almost-guaranteed" : "";
198
-
199
- return { type, date: ddate }
200
- }) : [];
201
-
202
- return (
203
- <Row key={itinerary.id} className="tour">
204
- <Col flex="1 0 25%" className="column">
205
- <div className="tour_image"> <img src={itinerary.images[0]}/> </div>
206
- </Col>
207
- <Col flex="1 0 55%" className="column">
208
- <div className="tour_details">
209
- <Space size={20} direction="vertical" style={{ display: 'flex' }}>
210
- <Space size={10} direction="vertical">
211
- <div className="tour_details_title"> {itinerary.caption} </div>
212
- <div className="tour_details_info">
213
- <span onClick={()=>this.showModal("descriptionModalOpen", "descriptionData", itinerary.description)}><Icon path={mdiInformationOutline} size={1}/></span>
214
- </div>
215
- <div>
216
- <span className="tour_details_country"> <Icon path={mdiMapMarkerOutline} size="18px" /> {itinerary.country} </span>
217
- <span className="tour_details_code" onClick={()=>this.showModal("itineraryModalOpen", "fileUrlData", itinerary.fileUrl)}> <Icon path={mdiFileDownload} size="18px" /> {itinerary.code} </span>
218
- </div>
219
- </Space>
220
- <div className="tour_details_dates">
221
- <Space size={6} direction="vertical" style={{ display: 'flex' }}>
222
- <label>Departure Date(s)</label>
223
- <div className="date-selector">
224
- {
225
- departureDates && departureDates.map((ddate, index) => <span key={index} style={{borderColor: getColorByType(ddate.type)}}> {ddate.date} </span>)
226
- }
227
- {
228
- departureDates.length >= 9 && <div className="show-all-dates" onClick={()=>this.showModal("dateModalOpen", "departureDatesData", departureDates)}> <Icon path={mdiDotsHorizontalCircleOutline} size="15px" /> Show All </div>
229
- }
207
+ {
208
+ itineraries.length > 0 ? (
209
+ <>
210
+ <div id="legends">
211
+ <Space size={17}>
212
+ {
213
+ legends.map(legend => <Badge key={legend.id} color={legend.color} text={legend.label} />)
214
+ }
215
+ </Space>
216
+ </div>
217
+ <div id="tours">
218
+ <Flex vertical gap={30}>
219
+ {
220
+ itineraries.map((itinerary:Itinerary) => {
221
+ const departureDates = itinerary.departureDate ? itinerary.departureDate.map(ddate => {
222
+ var type = (itinerary.guranteedDepartureDates && itinerary.guranteedDepartureDates.includes(ddate)) ? "guaranteed" :
223
+ (itinerary.almostGuaranteedDepartureDates && itinerary.almostGuaranteedDepartureDates.includes(ddate)) ? "almost-guaranteed" : "";
224
+
225
+ return { type, date: ddate }
226
+ }) : [];
227
+
228
+ return (
229
+ <Row key={itinerary.id} className="tour">
230
+ <Col flex="1 0 25%" className="column">
231
+ <div className="tour_image"> <img src={itinerary.images[0]}/> </div>
232
+ </Col>
233
+ <Col flex="1 0 55%" className="column">
234
+ <div className="tour_details">
235
+ <Space size={20} direction="vertical" style={{ display: 'flex' }}>
236
+ <Space size={10} direction="vertical">
237
+ <div className="tour_details_title"> {itinerary.caption} </div>
238
+ <div className="tour_details_info">
239
+ <span onClick={()=>this.showModal("descriptionModalOpen", "descriptionData", itinerary.description)}><Icon path={mdiInformationOutline} size={1}/></span>
240
+ </div>
241
+ <div>
242
+ <span className="tour_details_country"> <Icon path={mdiMapMarkerOutline} size="18px" /> {itinerary.country} </span>
243
+ <span className="tour_details_code" onClick={()=>this.showModal("itineraryModalOpen", "fileUrlData", itinerary.fileUrl)}> <Icon path={mdiFileDownload} size="18px" /> {itinerary.code} </span>
244
+ </div>
245
+ </Space>
246
+ <div className="tour_details_dates">
247
+ <Space size={6} direction="vertical" style={{ display: 'flex' }}>
248
+ <label>Departure Date(s)</label>
249
+ <div className="date-selector">
250
+ {
251
+ departureDates && departureDates.map((ddate, index) => <span key={index} style={{borderColor: getColorByType(ddate.type)}}> {ddate.date} </span>)
252
+ }
253
+ {
254
+ departureDates.length >= 9 && <div className="show-all-dates" onClick={()=>this.showModal("dateModalOpen", "departureDatesData", departureDates)}> <Icon path={mdiDotsHorizontalCircleOutline} size="15px" /> Show All </div>
255
+ }
256
+ </div>
257
+ </Space>
230
258
  </div>
231
259
  </Space>
232
260
  </div>
233
- </Space>
234
- </div>
235
- </Col>
236
- <Col flex="1 0 20%" className="column">
237
- <div className="tour_pricing">
238
- <Space size={20} direction="vertical" align="center">
239
- <Flex className="tour_pricing_details">
240
- <span>From</span>
241
- <span className="price">{itinerary.priceCurrency} {itinerary.price}</span>
242
- <span>All In</span>
243
- </Flex>
244
- <Link className="select-tour" to={`/app/itinerary/${itinerary.id}`}>Select</Link>
245
- </Space>
246
- </div>
247
- </Col>
248
- </Row>
249
- )
250
- })
251
- }
252
- </Flex>
253
- </div>
261
+ </Col>
262
+ <Col flex="1 0 20%" className="column">
263
+ <div className="tour_pricing">
264
+ <Space size={20} direction="vertical" align="center">
265
+ <Flex className="tour_pricing_details">
266
+ <span>From</span>
267
+ <span className="price">{itinerary.priceCurrency} {itinerary.price}</span>
268
+ <span>All In</span>
269
+ </Flex>
270
+ <Link className="select-tour" to={`/app/itinerary/${itinerary.id}`}>Select</Link>
271
+ </Space>
272
+ </div>
273
+ </Col>
274
+ </Row>
275
+ )
276
+ })
277
+ }
278
+ </Flex>
279
+ </div>
280
+ </>
281
+ ) : <h1 id="no-tours-found">No tour package is found.</h1>
282
+ }
283
+
254
284
  </div>
255
285
 
256
286
  <Modal title="Description" open={this.state.descriptionModalOpen} onCancel={()=>this.closeModal("descriptionModalOpen")} footer={null} width={1000} centered className="tour_details_description">
257
- <div className="pre-wrap">{ <span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(descriptionData) }} /> }</div>
287
+ <Markdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>{descriptionData}</Markdown>
258
288
  </Modal>
259
289
 
260
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, { useState } from "react";
2
- import { Button, Carousel, Col, Flex, Form, Layout, Modal, notification, Row, Select, Skeleton, Space } from "antd";
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},
@@ -86,11 +122,10 @@ class ShowPage extends React.Component <{params;}> {
86
122
  getAgents(){
87
123
  this.agentsApi.getAgents()
88
124
  .then((agentsData:Agent[]) => {
89
- const states = agentsData.filter(a => a.state != null).map(a => a.state);
90
- const cities = agentsData.filter(a => a.city != null).map(a => a.city);
91
- this.setState({agents: agentsData, allAgents: agentsData, loadingAgents:false, states, cities});
125
+ this.setState({agents: agentsData, allAgents: agentsData});
126
+ this.resetAgents(agentsData);
92
127
  })
93
- .finally(()=>this.setState({loading: false}))
128
+ .finally(()=>this.setState({loading: false}))
94
129
  .catch(error => {
95
130
  notification.error({ message: 'An error occured while loading agents.'});
96
131
  });
@@ -138,11 +173,13 @@ class ShowPage extends React.Component <{params;}> {
138
173
  this.setState({states: results});
139
174
  }
140
175
 
141
- resetAgents(){
142
- const allAgents:Agent[] = this.state.allAgents;
143
- const states = allAgents.filter(a => a.state != null).map(a => a.state);
144
- const cities = allAgents.filter(a => a.city != null).map(a => a.city);
145
- this.setState({states, cities});
176
+ resetAgents(agents?){
177
+ const allAgents:Agent[] = agents || this.state.allAgents;
178
+ const states = allAgents.filter(a => a.state?.replace(/\s/g, "") != '').map(a => a.state);
179
+ const cities = allAgents.filter(a => a.city?.replace(/\s/g, "") != '').map(a => a.city);
180
+ let uniqStates = [... new Set(states.map(x=>x))];
181
+ let uniqCities = [... new Set(cities.map(x=>x))];
182
+ this.setState({states: uniqStates, cities: uniqCities});
146
183
  }
147
184
 
148
185
  findAgents = (formValues) => {
@@ -170,6 +207,7 @@ class ShowPage extends React.Component <{params;}> {
170
207
  const dates = itinerary.tours.length == 0 ? [] : itinerary.tours.map((tour:any) => {
171
208
  return {id: tour.id, date: tour.departure_date, price: `${itinerary.priceCurrency} ${tour.price}`}
172
209
  });
210
+ const tourInclusives = thingsToKnow(itinerary.includings);
173
211
 
174
212
  return (
175
213
  <div id="show-page">
@@ -190,31 +228,21 @@ class ShowPage extends React.Component <{params;}> {
190
228
  </Headline>
191
229
 
192
230
  <div id="date-selector">
193
- <Carousel autoplay arrows dots={false} draggable slidesToScroll= {1} slidesToShow={8}
194
- prevArrow={
195
- <SlickButtonFix>
196
- <Icon path={mdiMenuLeft} size={2} />
197
- </SlickButtonFix>
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
+ })
198
244
  }
199
- nextArrow={
200
- <SlickButtonFix>
201
- <Icon path={mdiMenuRight} size={2} />
202
- </SlickButtonFix>
203
- }>
204
- {
205
- dates.map((tour, index) => {
206
- return <div key={index} className={`date-box ${selectedTour?.id == tour.id && "selected"}`} onClick={()=>this.selectTour(tour.id)}>
207
- <Flex justify="space-between" align="center" vertical gap={8}>
208
- <div className="date-box_date"> {tour.date} </div>
209
- <div>
210
- <span>From</span>
211
- <div className="date-box_price"> {tour.price} </div>
212
- </div>
213
- </Flex>
214
- </div>
215
- })
216
- }
217
- </Carousel>
245
+ </Slider>
218
246
  </div>
219
247
  </>
220
248
 
@@ -230,36 +258,41 @@ class ShowPage extends React.Component <{params;}> {
230
258
  ) : (
231
259
  <div className="details">
232
260
  <Flex gap={10} vertical>
233
- <div className="details-container">
234
- <div className="details-container_header"> Things to know </div>
235
- <div className="details-container_content">
236
- <div id="things-to-know">
237
- {
238
- thingsToKnow(itinerary.includings)
239
- .filter(item => item.visible)
240
- .map(item => (
241
- <div className="item">
242
- <div className="icon"> {item.icon} </div>
243
- <label>{item.label}</label>
244
- </div>
245
- ))
246
- }
247
- {
248
- selectedTour?.guaranteed_departure && (
249
- <div className="item guaranteed">
250
- <div className="icon"> <Icon path={mdiShieldAirplane} size={1} /></div>
251
- <label>Guaranteed Departure</label>
252
- </div>
253
- )
254
- }
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>
255
286
  </div>
256
- </div>
257
- </div>
287
+ )
288
+ }
258
289
 
259
290
  <div className="details-container">
260
291
  <div className="details-container_header"> Description </div>
261
292
  <div className="details-container_content">
262
- <div id="description" className={`pre-wrap ${isCollapsed && 'collapsed'}`}>{itinerary?.description}</div>
293
+ <div id="description" className={`${isCollapsed && 'collapsed'}`}>
294
+ <Markdown remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]}>{itinerary?.description}</Markdown>
295
+ </div>
263
296
  <Button className="toggle-description" color="default" variant="filled" block onClick={this.toggleDescription}>{ isCollapsed ? 'Expand' : 'Collapse'}
264
297
  <Icon path={isCollapsed ? mdiMenuDown : mdiMenuUp} size="19px" />
265
298
  </Button>
@@ -272,41 +305,45 @@ class ShowPage extends React.Component <{params;}> {
272
305
  <div className="details-container_header"> Schedule Flight(s) </div>
273
306
  <div className="details-container_content">
274
307
  <Flex gap={20} vertical>
275
- <div className="schedule">
276
- <div className="flight-date">
277
- <Icon path={mdiAirplane} size="12px" />
278
- <span>23/10/2024</span>
279
- </div>
280
- <div className="flight-details">
281
- <label> Kuala Lumpur (KUL) &gt; Guangzhou (CAN) </label>
282
- <div className="flight-details_airline">
283
- <img src="/iceholidays-assets/images/china_southern_airlines_icon.png" />
284
- <span>China Southern Airlines CZ366</span>
285
- </div>
286
- </div>
287
- <div className="flight-time">
288
- <Flex gap={13}>
289
- <div className="from">
290
- <label>08:55</label>
291
- <span>Kuala Lumpur (KUL)</span>
308
+ {
309
+ selectedTour?.flights.map((flight:any) => (
310
+ <div className="schedule" key={flight.id}>
311
+ <div className="flight-date">
312
+ <Icon path={mdiAirplane} size="12px" />
313
+ <span>{flight.departure_date}</span>
292
314
  </div>
293
- <div className="flight-time_icon">
294
- <img src="/iceholidays-assets/images/plane.png" />
315
+ <div className="flight-details">
316
+ <label> {flight.from_airport} &gt; {flight.to_airport} </label>
317
+ <div className="flight-details_airline">
318
+ <img src={flight.airline_logo} />
319
+ <span>{flight.airline}</span>
320
+ </div>
295
321
  </div>
296
- <div className="to">
297
- <label>12:55</label>
298
- <span>Guangzhou (CAN)</span>
322
+ <div className="flight-time">
323
+ <Flex gap={13}>
324
+ <div className="from">
325
+ <label>{flight.departure_time}</label>
326
+ <span>{flight.from_airport}</span>
327
+ </div>
328
+ <div className="flight-time_icon">
329
+ <img src="/iceholidays-assets/images/plane.png" />
330
+ </div>
331
+ <div className="to">
332
+ <label>{flight.arrival_time}</label>
333
+ <span>{flight.to_airport}</span>
334
+ </div>
335
+ </Flex>
299
336
  </div>
300
- </Flex>
301
- </div>
302
- </div>
337
+ </div>
338
+ ))
339
+ }
303
340
  </Flex>
304
341
  </div>
305
342
  </div>
306
343
  )
307
344
  }
308
345
 
309
- <PriceDetails priceCurrency={itinerary.priceCurrency} prices={selectedTour?.prices}/>
346
+ {selectedTour?.prices.length > 0 && <PriceDetails priceCurrency={itinerary.priceCurrency} prices={selectedTour?.prices}/>}
310
347
 
311
348
  </Flex>
312
349
  </div>