iceholidays-frontend 0.3.0 → 0.5.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 (108) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/iceholidays/frontend/actiontext.scss +119 -0
  3. data/app/assets/stylesheets/iceholidays/frontend/application.sass.scss +2194 -0
  4. data/app/assets/stylesheets/iceholidays/frontend/common.scss +317 -0
  5. data/app/assets/stylesheets/iceholidays/frontend/layout.scss +281 -0
  6. data/app/assets/stylesheets/iceholidays/frontend/utils/_antd_overrides.scss +122 -0
  7. data/app/assets/stylesheets/iceholidays/frontend/utils/_variables.scss +4 -0
  8. data/app/assets/stylesheets/iceholidays/frontend/widgets/filter_pills.scss +52 -0
  9. data/app/assets/stylesheets/iceholidays/frontend/widgets/search_bar.scss +116 -0
  10. data/app/controllers/iceholidays/frontend/posts_controller.rb +14 -0
  11. data/app/controllers/iceholidays/frontend/site_controller.rb +32 -0
  12. data/app/javascript/api-services/agents-api.service.ts +33 -0
  13. data/app/javascript/api-services/banners-api.service.ts +28 -0
  14. data/app/javascript/api-services/locations-api.service.ts +71 -0
  15. data/app/javascript/api-services/search-api.service.ts +16 -0
  16. data/app/javascript/api-services/series-api.service.ts +64 -0
  17. data/app/javascript/api-services/testimonials-api.service.ts +27 -0
  18. data/app/javascript/interfaces/agent.interface.ts +11 -0
  19. data/app/javascript/interfaces/banner.interface.ts +10 -0
  20. data/app/javascript/interfaces/country.interface.ts +19 -0
  21. data/app/javascript/interfaces/itinerary.interface.ts +111 -0
  22. data/app/javascript/interfaces/testimonial.interface.ts +6 -0
  23. data/app/javascript/react/App.tsx +32 -0
  24. data/app/javascript/react/components/Destinations.tsx +84 -143
  25. data/app/javascript/react/components/PriceDetails.tsx +146 -0
  26. data/app/javascript/react/components/Testimonials.tsx +68 -61
  27. data/app/javascript/react/components/shared/ContactAgentsForm.tsx +44 -0
  28. data/app/javascript/react/components/shared/Headline.tsx +30 -0
  29. data/app/javascript/react/components/shared/LocationDropdown.tsx +34 -0
  30. data/app/javascript/react/components/shared/LocationPostcards.tsx +52 -0
  31. data/app/javascript/react/components/shared/RibbonSection.tsx +21 -0
  32. data/app/javascript/react/index.js +3 -5
  33. data/app/javascript/react/layouts/MainFooter.tsx +97 -0
  34. data/app/javascript/react/layouts/MainHeader.tsx +83 -0
  35. data/app/javascript/react/layouts/MainLayout.tsx +21 -0
  36. data/app/javascript/react/pages/AboutUsPage.tsx +95 -0
  37. data/app/javascript/react/pages/BlogPage.tsx +81 -0
  38. data/app/javascript/react/pages/BlogShowPage.tsx +43 -0
  39. data/app/javascript/react/pages/ContactAgentsPage.tsx +185 -0
  40. data/app/javascript/react/pages/ContactUsPage.tsx +122 -0
  41. data/app/javascript/react/pages/CountriesPage.tsx +57 -0
  42. data/app/javascript/react/pages/Homepage.tsx +100 -0
  43. data/app/javascript/react/pages/ListingPage.tsx +292 -0
  44. data/app/javascript/react/pages/ShowPage.tsx +402 -0
  45. data/app/javascript/react/widgets/FilterPills.tsx +111 -0
  46. data/app/javascript/react/widgets/SearchBarWidget.tsx +58 -0
  47. data/app/views/iceholidays/frontend/posts/index.html.erb +9 -0
  48. data/app/views/iceholidays/frontend/posts/show.html.erb +2 -0
  49. data/app/views/iceholidays/frontend/site/index.html.erb +1 -24
  50. data/app/views/layouts/iceholidays/frontend/application.html.erb +2 -6
  51. data/config/routes.rb +10 -0
  52. data/lib/iceholidays/frontend/version.rb +1 -1
  53. data/public/iceholidays-assets/application.css +2638 -0
  54. data/public/iceholidays-assets/application.js +212 -651
  55. data/public/iceholidays-assets/application.js.map +4 -4
  56. data/public/iceholidays-assets/images/about-us_logo_mobile.png +0 -0
  57. data/public/iceholidays-assets/images/about_us.png +0 -0
  58. data/public/iceholidays-assets/images/about_us2.png +0 -0
  59. data/public/iceholidays-assets/images/blog.png +0 -0
  60. data/public/iceholidays-assets/images/blog1.png +0 -0
  61. data/public/iceholidays-assets/images/certificate1.png +0 -0
  62. data/public/iceholidays-assets/images/certificate2.png +0 -0
  63. data/public/iceholidays-assets/images/china_southern_airlines.png +0 -0
  64. data/public/iceholidays-assets/images/china_southern_airlines_icon.png +0 -0
  65. data/public/iceholidays-assets/images/competitiveness.png +0 -0
  66. data/public/iceholidays-assets/images/contact_agents.png +0 -0
  67. data/public/iceholidays-assets/images/contact_us.png +0 -0
  68. data/public/iceholidays-assets/images/contact_us_form.png +0 -0
  69. data/public/iceholidays-assets/images/destinations_logo.png +0 -0
  70. data/public/iceholidays-assets/images/ethical.png +0 -0
  71. data/public/iceholidays-assets/images/footer-bg_mobile.png +0 -0
  72. data/public/iceholidays-assets/images/hw_logo.png +0 -0
  73. data/public/iceholidays-assets/images/innovative.png +0 -0
  74. data/public/iceholidays-assets/images/logo_mobile.png +0 -0
  75. data/public/iceholidays-assets/images/plane.png +0 -0
  76. data/public/iceholidays-assets/images/social/ico_fb.png +0 -0
  77. data/public/iceholidays-assets/images/social/ico_ig.png +0 -0
  78. data/public/iceholidays-assets/images/social/ico_twitter.png +0 -0
  79. data/public/iceholidays-assets/images/social/ico_yt.png +0 -0
  80. data/public/iceholidays-assets/images/social.png +0 -0
  81. metadata +74 -71
  82. data/app/assets/stylesheets/iceholidays/frontend/application.scss +0 -904
  83. data/app/javascript/react/components/Homepage.tsx +0 -15
  84. data/app/javascript/react/components/HomepageBanner.tsx +0 -62
  85. data/app/views/layouts/iceholidays/frontend/shared/_footer.html.erb +0 -42
  86. data/app/views/layouts/iceholidays/frontend/shared/_header.html.erb +0 -20
  87. data/public/iceholidays-assets/images/Frame71.png +0 -0
  88. data/public/iceholidays-assets/images/africa.png +0 -0
  89. data/public/iceholidays-assets/images/banner1.png +0 -0
  90. data/public/iceholidays-assets/images/banner2.png +0 -0
  91. data/public/iceholidays-assets/images/china.png +0 -0
  92. data/public/iceholidays-assets/images/china2.png +0 -0
  93. data/public/iceholidays-assets/images/guangzhou.png +0 -0
  94. data/public/iceholidays-assets/images/guilin.png +0 -0
  95. data/public/iceholidays-assets/images/harbin.png +0 -0
  96. data/public/iceholidays-assets/images/hongkong.png +0 -0
  97. data/public/iceholidays-assets/images/inner_mongolia.png +0 -0
  98. data/public/iceholidays-assets/images/jiangxi.png +0 -0
  99. data/public/iceholidays-assets/images/kenya.png +0 -0
  100. data/public/iceholidays-assets/images/kenya2.png +0 -0
  101. data/public/iceholidays-assets/images/kunming.png +0 -0
  102. data/public/iceholidays-assets/images/slikroad.png +0 -0
  103. data/public/iceholidays-assets/images/southafrica.png +0 -0
  104. data/public/iceholidays-assets/images/tanzania.png +0 -0
  105. data/public/iceholidays-assets/images/uganda.png +0 -0
  106. /data/public/iceholidays-assets/images/{Group_71.png → about-us_logo.png} +0 -0
  107. /data/public/iceholidays-assets/images/{chongqing.png → china_listings_cover.png} +0 -0
  108. /data/public/iceholidays-assets/images/{logo_container.png → logo.png} +0 -0
@@ -0,0 +1,402 @@
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";
4
+ 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
+ import Icon from "@mdi/react";
6
+ import Headline from "../components/shared/Headline";
7
+ import { Content } from "antd/es/layout/layout";
8
+ import Sider from "antd/es/layout/Sider";
9
+ import SeriesApi from "../../api-services/series-api.service";
10
+ import { useParams } from "react-router-dom";
11
+ import { Itinerary } from "../../interfaces/itinerary.interface";
12
+ import AgentsApi from "../../api-services/agents-api.service";
13
+ import { Agent } from "../../interfaces/agent.interface";
14
+ import PriceDetails from "../components/PriceDetails";
15
+ import ContactAgentsForm from "../components/shared/ContactAgentsForm";
16
+
17
+ const breadcrumbs = [
18
+ { title: 'Home' },
19
+ ]
20
+
21
+ const thingsToKnow = (inclusionsData) => {
22
+ return [
23
+ {icon: <Icon path={mdiShieldAccount} size={1} />, label: "Free Travel Insurance", visible: inclusionsData.acf},
24
+ {icon: <Icon path={mdiReceiptText} size={1} /> , label: "Airport Taxes", visible: inclusionsData.airport_taxes},
25
+ {icon: <Icon path={mdiFlagTriangle} size={1} />, label: "Tour Leader", visible: inclusionsData.tour_leader},
26
+ {icon: <Icon path={mdiBagChecked} size={1} />, label: "Check-in Baggage", visible: inclusionsData.luggage},
27
+ {icon: <Icon path={mdiSilverwareForkKnife} size={1} />, label: "Meal Onboard", visible: inclusionsData.meal_onboard},
28
+ {icon: <Icon path={mdiBedKing} size={1} />, label: "Hotel", visible: inclusionsData.hotel},
29
+ {icon: <Icon path={mdiAccountCash} size={1} />, label: "Gratuities", visible: inclusionsData.gratuities},
30
+ {icon: <Icon path={mdiAccountGroup} size={1} />, label: "Group Departure", visible: inclusionsData.group_departure},
31
+ {icon: <Icon path={mdiFileDownload} size={1} />, label: "Itinerary Download EN | CN"}
32
+ ];
33
+ }
34
+
35
+ const layoutStyle = {
36
+ overflow: 'hidden',
37
+ };
38
+
39
+ const siderStyle = {
40
+ backgroundColor: 'transparent'
41
+ }
42
+
43
+
44
+ function withParams(Component) {
45
+ return props => <Component {...props} params={useParams()} />;
46
+ }
47
+
48
+ class ShowPage extends React.Component <{params;}> {
49
+ seriesApi = new SeriesApi;
50
+ agentsApi = new AgentsApi;
51
+
52
+ state = {
53
+ loading: true,
54
+ itinerary: {includings:{}, tours:[], priceCurrency:"", country: "", description: "", coverImage: ""},
55
+ selectedTour:{id: null,guaranteed_departure:false, flights:[], caption:"", code:"", prices: []},
56
+ setIsModalOpen: false,
57
+ loadingAgents: true,
58
+ allAgents: [],
59
+ agents: [],
60
+ states: [],
61
+ cities: [],
62
+ showAgentsResults: false,
63
+ agentData: { name: "", image: "", address: "", phone: "", whatsapp: "", email: "", city: "", state: "" },
64
+ isCollapsed: true,
65
+ showContactAgent: false
66
+ }
67
+
68
+
69
+ componentDidMount() {
70
+ this.seriesApi.getItinerary(this.props.params.id)
71
+ .then((itineraryData:Itinerary) => {
72
+ this.setState({itinerary: itineraryData});
73
+ if(itineraryData.tours && itineraryData.tours?.length > 0){
74
+ this.setState({selectedTour: itineraryData.tours[0]});
75
+ }
76
+ })
77
+ .finally(()=>this.setState({loading: false}))
78
+ .catch(error => {
79
+ notification.error({ message: 'An error occured while loading countries.'});
80
+ });
81
+
82
+ this.getAgents();
83
+ }
84
+
85
+
86
+ getAgents(){
87
+ this.agentsApi.getAgents()
88
+ .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});
92
+ })
93
+ .finally(()=>this.setState({loading: false}))
94
+ .catch(error => {
95
+ notification.error({ message: 'An error occured while loading agents.'});
96
+ });
97
+ }
98
+
99
+ selectTour(tourId:number){
100
+ const selectedTour:any = this.state.itinerary.tours.find((tour:any) => tour.id == tourId);
101
+ if(selectedTour){
102
+ this.setState({selectedTour});
103
+ }
104
+ }
105
+
106
+ showModal(agentData){
107
+ this.setState({setIsModalOpen: true, agentData: agentData});
108
+ };
109
+
110
+ closeModal(){
111
+ this.setState({setIsModalOpen: false});
112
+ };
113
+
114
+
115
+ filterCities = (searchStr) => {
116
+ const allAgents:Agent[] = this.state.allAgents;
117
+
118
+ if(searchStr == undefined){
119
+ this.resetAgents();
120
+ return;
121
+ }
122
+ const agents:Agent[] = allAgents.filter(a => a.state == searchStr);
123
+ const results = agents.map(a => a.city);
124
+ this.setState({cities: results});
125
+ }
126
+
127
+
128
+ filterStates= (searchStr) => {
129
+ const allAgents:Agent[] = this.state.allAgents;
130
+
131
+ if(searchStr == undefined){
132
+ this.resetAgents();
133
+ return;
134
+ }
135
+
136
+ const agents:Agent[] = allAgents.filter(a => a.city == searchStr);
137
+ const results = agents.map(a => a.state);
138
+ this.setState({states: results});
139
+ }
140
+
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});
146
+ }
147
+
148
+ findAgents = (formValues) => {
149
+ const allAgents:Agent[] = this.state.allAgents;
150
+ const agents = allAgents.filter(a =>
151
+ (formValues.state == undefined || a.state === formValues.state) &&
152
+ (formValues.city == undefined || a.city === formValues.city));
153
+ this.setState({agents, showAgentsResults: true})
154
+ }
155
+
156
+
157
+ toggleDescription = () => {
158
+ const collapsed = this.state.isCollapsed;
159
+ this.setState({isCollapsed: !collapsed});
160
+ }
161
+
162
+ toggleContactAgent = () => {
163
+ const collapsed = this.state.showContactAgent;
164
+ this.setState({showContactAgent: !collapsed});
165
+ }
166
+
167
+
168
+ render(){
169
+ const {loading, itinerary, selectedTour, agents, states, cities, agentData, isCollapsed, showContactAgent} = this.state;
170
+ const dates = itinerary.tours.length == 0 ? [] : itinerary.tours.map((tour:any) => {
171
+ return {id: tour.id, date: tour.departure_date, price: `${itinerary.priceCurrency} ${tour.price}`}
172
+ });
173
+
174
+ return (
175
+ <div id="show-page">
176
+
177
+ <Layout style={layoutStyle}>
178
+
179
+ <>
180
+ <Headline showSearch={false} bannerImage={itinerary.coverImage} breadcrumbs={breadcrumbs}>
181
+ <div id="show-page_header">
182
+ <Flex justify="space-between" vertical gap={45}>
183
+ <h1>{selectedTour?.caption}</h1>
184
+ <div className="tour_details">
185
+ <span className="tour_details_country"> <Icon path={mdiMapMarkerOutline} size="16px" /> {itinerary?.country} </span>
186
+ <span className="tour_details_code"> <Icon path={mdiFileDownload} size="16px" /> {selectedTour?.code} </span>
187
+ </div>
188
+ </Flex>
189
+ </div>
190
+ </Headline>
191
+
192
+ <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>
198
+ }
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>
218
+ </div>
219
+ </>
220
+
221
+ <Layout id="show-page_body">
222
+ <Content>
223
+ {
224
+ loading ? (
225
+ <Space direction="vertical" style={{width: '100%'}}>
226
+ <Skeleton/>
227
+ <Skeleton/>
228
+ <Skeleton/>
229
+ </Space>
230
+ ) : (
231
+ <div className="details">
232
+ <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
+ }
255
+ </div>
256
+ </div>
257
+ </div>
258
+
259
+ <div className="details-container">
260
+ <div className="details-container_header"> Description </div>
261
+ <div className="details-container_content">
262
+ <div id="description" className={`pre-wrap ${isCollapsed && 'collapsed'}`}>{itinerary?.description}</div>
263
+ <Button className="toggle-description" color="default" variant="filled" block onClick={this.toggleDescription}>{ isCollapsed ? 'Expand' : 'Collapse'}
264
+ <Icon path={isCollapsed ? mdiMenuDown : mdiMenuUp} size="19px" />
265
+ </Button>
266
+ </div>
267
+ </div>
268
+
269
+ {
270
+ selectedTour?.flights.length > 0 && (
271
+ <div className="details-container">
272
+ <div className="details-container_header"> Schedule Flight(s) </div>
273
+ <div className="details-container_content">
274
+ <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>
292
+ </div>
293
+ <div className="flight-time_icon">
294
+ <img src="/iceholidays-assets/images/plane.png" />
295
+ </div>
296
+ <div className="to">
297
+ <label>12:55</label>
298
+ <span>Guangzhou (CAN)</span>
299
+ </div>
300
+ </Flex>
301
+ </div>
302
+ </div>
303
+ </Flex>
304
+ </div>
305
+ </div>
306
+ )
307
+ }
308
+
309
+ <PriceDetails priceCurrency={itinerary.priceCurrency} prices={selectedTour?.prices}/>
310
+
311
+ </Flex>
312
+ </div>
313
+ )
314
+ }
315
+ </Content>
316
+ <Sider width="265px" style={siderStyle} className={`contact-agent-sider ${showContactAgent && 'show-contact-agent-mobile'}`}>
317
+ <div className="toggle_contact-agent" onClick={this.toggleContactAgent}>
318
+ <span>Contact Agent</span>
319
+ </div>
320
+ <div className="contact-agent">
321
+ <div className="contact-agent_header">
322
+ <span>Contact Agent</span>
323
+ <Button className="collapse-contact-agent" color="default" variant="text"
324
+ icon={<Icon path={mdiClose} size="19px" />}
325
+ onClick={this.toggleContactAgent}/>
326
+ </div>
327
+ <div className="contact-agent_content">
328
+ <ContactAgentsForm
329
+ states={states}
330
+ cities={cities}
331
+ findAgents={this.findAgents}
332
+ filterCities={this.filterCities}
333
+ filterStates={this.filterStates}/>
334
+
335
+ {
336
+ this.state.showAgentsResults && (
337
+ <div className="search-results">
338
+ <Flex vertical gap={20}>
339
+ {
340
+ agents.length > 0 && agents.map((agent:Agent) => {
341
+ return (
342
+ <Row gutter={2} justify="space-between" className="agent-info">
343
+ <Col flex="180px"><span className="company" onClick={()=>this.showModal(agent)}>
344
+ {agent.name}</span>
345
+ </Col>
346
+ <Col flex="none"><Button color="default" variant="filled" shape="circle" size="small" icon={<Icon path={mdiPhoneInTalkOutline} size="16px" />}/></Col>
347
+ <Col flex="none"><Button color="default" variant="filled" shape="circle" size="small" icon={<Icon path={mdiEmailOutline} size="16px" />}/></Col>
348
+ <Col>
349
+ <div className="contact-numbers">
350
+ <Button size="small" color="primary" variant="outlined">
351
+ <Icon path={mdiWhatsapp} size="12px" />
352
+ {agent.whatsapp}
353
+ </Button>
354
+ </div>
355
+ </Col>
356
+ </Row>
357
+ )
358
+ })
359
+ }
360
+ </Flex>
361
+ </div>
362
+ )
363
+ }
364
+ </div>
365
+ </div>
366
+ </Sider>
367
+ </Layout>
368
+ </Layout>
369
+
370
+
371
+ <Modal title={<img src={agentData?.image}/>} open={this.state.setIsModalOpen} onCancel={()=>this.closeModal()} footer={null} width={325} centered className="agent-full-contact-details">
372
+ <h2> {agentData?.name} </h2>
373
+ <div className="contact-details">
374
+ <Row gutter={[15, 15]} align="middle">
375
+ <Col span={24}>
376
+ <Icon path={mdiMapMarker} size="15px" />
377
+ <span>{agentData?.address}</span>
378
+ </Col>
379
+ {/* </Row>
380
+ <Row gutter={15} align="middle"> */}
381
+ <Col xs={24} sm={24} lg={6}>
382
+ <Icon path={mdiPhoneInTalkOutline} size="15px" />
383
+ <span>{agentData?.phone}</span>
384
+ </Col>
385
+ <Col xs={24} sm={24} lg={6}>
386
+ <Icon path={mdiWhatsapp} size="15px" />
387
+ <span>{agentData?.whatsapp}</span>
388
+ </Col>
389
+ <Col xs={24} sm={24} lg={9}>
390
+ <Icon path={mdiEmailOutline} size="15px" />
391
+ <span>{agentData?.email}</span>
392
+ </Col>
393
+ </Row>
394
+ </div>
395
+ </Modal>
396
+ </div>
397
+ )
398
+ }
399
+
400
+ }
401
+
402
+ export default withParams(ShowPage);
@@ -0,0 +1,111 @@
1
+ import { Col, Row, Space } from "antd";
2
+ import React, { useEffect, useState } from "react";
3
+
4
+
5
+ const years = () => {
6
+ const currentYear = new Date().getFullYear();
7
+ const nextYear = new Date(new Date().setFullYear(currentYear + 1)).getFullYear();
8
+ return [currentYear, nextYear]
9
+ };
10
+ const months = Array.from({length: 12}, (item, i) => {
11
+ return {
12
+ shortMonth: new Date(0, i).toLocaleString('en-US', {month: 'short'}),
13
+ name: new Date(0, i).toLocaleString('en-US', {month: 'long'})
14
+ }
15
+ });
16
+
17
+
18
+ function FilterPills(
19
+ props: {
20
+ items:any[];
21
+ title?:string;
22
+ initialValue:{keyword?:string, year?:string, month?: string, location_id?:any}
23
+ bindLabel?:string;
24
+ bindValue?:string;
25
+ allOption?:boolean;
26
+ selectFilter;
27
+ }){
28
+
29
+ const { items, title, bindLabel, bindValue, initialValue, allOption } = props;
30
+
31
+ const [selected, setSelected] = useState(initialValue);
32
+
33
+ const allHasValues = () => {
34
+ const {keyword, ...noKeyword} = selected;
35
+ return Object.entries(noKeyword).every(o => o[1] != "");
36
+ }
37
+ const [collapseFilters, setcollapseFilters] = useState(allHasValues);
38
+
39
+ const selectFilter = (label, value) => {
40
+ selected[label] = value;
41
+ setSelected(selected);
42
+
43
+ setcollapseFilters(allHasValues);
44
+
45
+ props.selectFilter(selected);
46
+ }
47
+
48
+ return (
49
+ <>
50
+ {
51
+ collapseFilters==false && (
52
+
53
+ <Space size={12} direction="vertical">
54
+ {
55
+ items && (
56
+ <>
57
+ { title && <div className="filter-title"> {title} </div> }
58
+ <Space size={10} wrap>
59
+ {
60
+ allOption && <div className="filter-pill selected"> <span> All </span></div>
61
+ }
62
+ {
63
+ items.map((item, index) => {
64
+ //special case for location id
65
+ const id:any = bindValue == "location_id" ? "id" : bindValue;
66
+ const label = bindLabel ? item[bindLabel] : item;
67
+ const value = bindValue ? item[id] : item;
68
+ const selectedItem = selected && bindValue ? selected[bindValue]: item ;
69
+ return <div key={index} onClick={()=>selectFilter(bindValue, value)} className={`filter-pill ${selectedItem == value && 'selected'}`}> <span>{ label } </span></div>
70
+ })
71
+ }
72
+ </Space>
73
+ </>
74
+ )
75
+ }
76
+
77
+ <div className="filter-title"> Year </div>
78
+ <Space size={10} wrap>
79
+ {
80
+ allOption && <div className="filter-pill selected"> <span> All </span></div>
81
+ }
82
+ {
83
+ years().map((year, index) => {
84
+ const selectedItem = selected && selected.year;
85
+ return <div key={index} onClick={()=>selectFilter("year", year)} className={`filter-pill default-filter ${selectedItem == year.toString() && 'selected'}`}> <span>{year} </span></div>
86
+ })
87
+ }
88
+ </Space>
89
+
90
+ <div className="filter-title"> Month </div>
91
+ <Row gutter={[10, 10]} justify="space-between">
92
+ {
93
+ allOption && <Col className="filter-pill selected"> <span> All </span></Col>
94
+ }
95
+ {
96
+ months.map((month, index) => {
97
+ const selectedItem = selected && selected.month ;
98
+ return <Col xs={8} md={6} lg={4}> <div key={index} onClick={()=>selectFilter("month", month.shortMonth)} className={`filter-pill default-filter ${selectedItem?.toString() == month.shortMonth && 'selected'}`}> <span>{month.name} </span></div> </Col>
99
+ })
100
+ }
101
+ </Row>
102
+ </Space>
103
+ )
104
+
105
+ }
106
+ </>
107
+ )
108
+
109
+ }
110
+
111
+ export default FilterPills;
@@ -0,0 +1,58 @@
1
+ import { Form, Space, Input, DatePicker, Button, Flex } from "antd";
2
+ import React from "react";
3
+ import SearchApi from "../../api-services/search-api.service";
4
+ import { useNavigate, useSearchParams } from "react-router-dom";
5
+ import dayjs from "dayjs";
6
+
7
+ const monthFormat = "MMM YYYY";
8
+
9
+ function SearchBarWidget() {
10
+ const searchApi = new SearchApi;
11
+ const navigate= useNavigate();
12
+ const [searchParams] = useSearchParams();
13
+
14
+ const save = (formValues) => {
15
+ let searchObj = {};
16
+ if(formValues.keyword) searchObj = {keyword: formValues.keyword};
17
+
18
+ if(formValues.date){
19
+ searchObj["year"] = formValues.date.$y;
20
+ searchObj["month"] = new Date(formValues.date).toLocaleString('default', { month: 'short' });
21
+ }
22
+
23
+ const searchParams = new URLSearchParams(searchObj).toString();
24
+ navigate(`/app/listing?${searchParams}`);
25
+ navigate(0);
26
+
27
+ // searchApi.search(formValues.keyword, formValues.month)
28
+ // .then(result => {
29
+ // navigate(`/app/listing?keyword=${formValues.keyword}`);
30
+ // })
31
+ }
32
+
33
+
34
+ return (
35
+ <div id="search-bar">
36
+ <img src="/iceholidays-assets/images/Rectangle49.png"/>
37
+ <div id="search-bar-widget">
38
+ <Form
39
+ onFinish={save}
40
+ layout="vertical"
41
+ >
42
+ <Form.Item wrapperCol={{ span: 24 }} label="Search" name="keyword">
43
+ <Input placeholder="Destinations, Attractions, etc"/>
44
+ </Form.Item>
45
+ <Form.Item wrapperCol={{ span: 24 }} label="Travel Period" name="date">
46
+ <DatePicker picker="month" format={monthFormat} inputReadOnly/>
47
+ </Form.Item>
48
+ <Flex align="flex-end" justify="center">
49
+ <Button type="primary" className="search-button" htmlType="submit">Search</Button>
50
+ </Flex>
51
+ </Form>
52
+ </div>
53
+ <img src="/iceholidays-assets/images/Rectangle49.png" className="flip"/>
54
+ </div>
55
+ )
56
+ }
57
+
58
+ export default SearchBarWidget;
@@ -0,0 +1,9 @@
1
+ <% @posts.each do |post| %>
2
+ <%= post.title %>
3
+
4
+ <% if post.thumbnail.attached? %>
5
+ <%= image_tag( post.thumbnail.blob.service_name.in?(%w[local test test_fixtures]) ? main_app.url_for(post.thumbnail) : post.thumbnail.attachment.url, height:300) %>
6
+ <% else %>
7
+ <%#= image_tag('nothumbnail.png', width:300) %>
8
+ <%end%>
9
+ <% end %>
@@ -0,0 +1,2 @@
1
+ <%= @post.title %>
2
+ <%= @post.content %>
@@ -1,24 +1 @@
1
- <%= react_component "Homepage"%>
2
-
3
-
4
- <div id="about-signature-tours" style="background: url(<%=asset_path('/iceholidays-assets/images/about_st_bg.png')%>) no-repeat 70% 7%, linear-gradient(65.77deg, rgba(170, 133, 62, 0.5) 33.44%, rgba(249, 225, 151, 0.5) 67.37%)">
5
- <div>
6
- <h1>ABOUT SIGNATURE TOURS</h1>
7
- <p>
8
- From charming accommodations to curated cultural encounters, every aspect is thoughtfully crafted to elevate your travel experience. At The Signature Tours, we believe in the S-Tour’s philosophy —where every moment is a signature memory.
9
- </p>
10
- <%=image_tag("/iceholidays-assets/images/Group_71.png")%>
11
- <div id="about-st-feature">
12
- <div style="max-width: 449px">
13
- <p>Every experience is meticulously designed to create memories that last a lifetime. Our journeys go beyond the ordinary, offering experiences that are as unique as you are.</p>
14
- </div>
15
- <div id="feature">
16
- <%=image_tag("/iceholidays-assets/images/feature.jpg")%>
17
- <div id="st-brand">
18
- <span>Signature Tours</span>
19
- <%=image_tag("/iceholidays-assets/images/logomark.png", id: "st-logo")%>
20
- </div>
21
- </div>
22
- </div>
23
- </div>
24
- </div>
1
+ <%= react_component "App"%>
@@ -4,21 +4,17 @@
4
4
  <title>Iceholidays frontend</title>
5
5
  <%= csrf_meta_tags %>
6
6
  <%= csp_meta_tag %>
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
8
 
8
9
  <%= yield :head %>
9
10
 
10
11
  <%= javascript_include_tag "/iceholidays-assets/application", "data-turbo-track": "reload", type: "module" %>
11
- <%= stylesheet_link_tag "iceholidays/frontend/application", media: "all" %>
12
+ <%= stylesheet_link_tag "/iceholidays-assets/application", media: "all" %>
12
13
  </head>
13
14
  <body>
14
-
15
- <%=render partial:"layouts/iceholidays/frontend/shared/header"%>
16
-
17
15
  <main>
18
16
  <%= yield %>
19
17
  </main>
20
-
21
- <%=render partial:"layouts/iceholidays/frontend/shared/footer"%>
22
18
  </body>
23
19
 
24
20
  </html>
data/config/routes.rb CHANGED
@@ -1,3 +1,13 @@
1
1
  Iceholidays::Frontend::Engine.routes.draw do
2
2
  root to: 'site#index'
3
+
4
+ get '/listing' => 'site#listing', as: :listing
5
+ get '/itinerary/:id' => 'site#show', as: :show
6
+ get '/about-us' => 'site#about_us', as: :about_us
7
+ get '/countries' => 'site#countries', as: :countries
8
+ get '/blog' => 'site#blog', as: :blog
9
+ get '/blog/1' => 'site#show_blog', as: :show_blog
10
+ get '/contact-agents' => 'site#contact_agents', as: :contact_agents
11
+ get '/contact-us' => 'site#contact_us', as: :contact_us
12
+ resources :posts, only:[:index, :show]
3
13
  end
@@ -1,5 +1,5 @@
1
1
  module Iceholidays
2
2
  module Frontend
3
- VERSION = "0.3.0"
3
+ VERSION = "0.5.0"
4
4
  end
5
5
  end