iceholidays-frontend 0.3.0 → 0.4.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 (76) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/iceholidays/frontend/application.sass.scss +1588 -0
  3. data/app/assets/stylesheets/iceholidays/frontend/common.scss +233 -0
  4. data/app/assets/stylesheets/iceholidays/frontend/layout.scss +188 -0
  5. data/app/assets/stylesheets/iceholidays/frontend/utils/_antd_overrides.scss +107 -0
  6. data/app/assets/stylesheets/iceholidays/frontend/utils/_variables.scss +3 -0
  7. data/app/assets/stylesheets/iceholidays/frontend/widgets/filter_pills.scss +45 -0
  8. data/app/assets/stylesheets/iceholidays/frontend/widgets/search_bar.scss +112 -0
  9. data/app/controllers/iceholidays/frontend/site_controller.rb +32 -0
  10. data/app/javascript/api-services/banners-api.service.ts +28 -0
  11. data/app/javascript/api-services/locations-api.service.ts +49 -0
  12. data/app/javascript/api-services/search-api.service.ts +16 -0
  13. data/app/javascript/api-services/series-api.service.ts +44 -0
  14. data/app/javascript/api-services/testimonials-api.service.ts +27 -0
  15. data/app/javascript/interfaces/banner.interface.ts +10 -0
  16. data/app/javascript/interfaces/country.interface.ts +18 -0
  17. data/app/javascript/interfaces/itinerary.interface.ts +18 -0
  18. data/app/javascript/interfaces/testimonial.interface.ts +6 -0
  19. data/app/javascript/react/App.tsx +32 -0
  20. data/app/javascript/react/components/Destinations.tsx +72 -141
  21. data/app/javascript/react/components/Testimonials.tsx +68 -61
  22. data/app/javascript/react/components/shared/Headline.tsx +29 -0
  23. data/app/javascript/react/components/shared/Postcard.tsx +31 -0
  24. data/app/javascript/react/components/shared/RibbonSection.tsx +21 -0
  25. data/app/javascript/react/index.js +3 -5
  26. data/app/javascript/react/layouts/MainFooter.tsx +72 -0
  27. data/app/javascript/react/layouts/MainHeader.tsx +45 -0
  28. data/app/javascript/react/layouts/MainLayout.tsx +21 -0
  29. data/app/javascript/react/pages/AboutUsPage.tsx +95 -0
  30. data/app/javascript/react/pages/BlogPage.tsx +79 -0
  31. data/app/javascript/react/pages/BlogShowPage.tsx +43 -0
  32. data/app/javascript/react/pages/ContactAgentsPage.tsx +16 -0
  33. data/app/javascript/react/pages/ContactUsPage.tsx +122 -0
  34. data/app/javascript/react/pages/CountriesPage.tsx +62 -0
  35. data/app/javascript/react/pages/Homepage.tsx +90 -0
  36. data/app/javascript/react/pages/ListingPage.tsx +246 -0
  37. data/app/javascript/react/pages/ShowPage.tsx +397 -0
  38. data/app/javascript/react/widgets/FilterPills.tsx +77 -0
  39. data/app/javascript/react/widgets/SearchBarWidget.tsx +42 -0
  40. data/app/views/iceholidays/frontend/site/index.html.erb +1 -24
  41. data/app/views/layouts/iceholidays/frontend/application.html.erb +2 -6
  42. data/config/routes.rb +9 -0
  43. data/lib/iceholidays/frontend/version.rb +1 -1
  44. data/public/iceholidays-assets/application.css +1873 -0
  45. data/public/iceholidays-assets/application.js +225 -651
  46. data/public/iceholidays-assets/application.js.map +4 -4
  47. data/public/iceholidays-assets/images/8D7N.png +0 -0
  48. data/public/iceholidays-assets/images/about_us.png +0 -0
  49. data/public/iceholidays-assets/images/about_us2.png +0 -0
  50. data/public/iceholidays-assets/images/blog.png +0 -0
  51. data/public/iceholidays-assets/images/blog1.png +0 -0
  52. data/public/iceholidays-assets/images/certificate1.png +0 -0
  53. data/public/iceholidays-assets/images/certificate2.png +0 -0
  54. data/public/iceholidays-assets/images/china_listings_cover.png +0 -0
  55. data/public/iceholidays-assets/images/china_southern_airlines.png +0 -0
  56. data/public/iceholidays-assets/images/china_southern_airlines_icon.png +0 -0
  57. data/public/iceholidays-assets/images/competitiveness.png +0 -0
  58. data/public/iceholidays-assets/images/contact_agents.png +0 -0
  59. data/public/iceholidays-assets/images/contact_us.png +0 -0
  60. data/public/iceholidays-assets/images/contact_us_form.png +0 -0
  61. data/public/iceholidays-assets/images/ethical.png +0 -0
  62. data/public/iceholidays-assets/images/hw_logo.png +0 -0
  63. data/public/iceholidays-assets/images/innovative.png +0 -0
  64. data/public/iceholidays-assets/images/plane.png +0 -0
  65. data/public/iceholidays-assets/images/social/ico_fb.png +0 -0
  66. data/public/iceholidays-assets/images/social/ico_ig.png +0 -0
  67. data/public/iceholidays-assets/images/social/ico_twitter.png +0 -0
  68. data/public/iceholidays-assets/images/social/ico_yt.png +0 -0
  69. data/public/iceholidays-assets/images/social.png +0 -0
  70. data/public/iceholidays-assets/images/tour1.png +0 -0
  71. metadata +61 -49
  72. data/app/assets/stylesheets/iceholidays/frontend/application.scss +0 -904
  73. data/app/javascript/react/components/Homepage.tsx +0 -15
  74. data/app/javascript/react/components/HomepageBanner.tsx +0 -62
  75. data/app/views/layouts/iceholidays/frontend/shared/_footer.html.erb +0 -42
  76. data/app/views/layouts/iceholidays/frontend/shared/_header.html.erb +0 -20
@@ -0,0 +1,90 @@
1
+ import React from "react";
2
+ import Testimonials from "../components/Testimonials";
3
+ import Destinations from "../components/Destinations";
4
+ import SearchBarWidget from "../widgets/SearchBarWidget";
5
+ import { Carousel, notification } from "antd";
6
+ import SlickButtonFix from "../components/shared/SlickButtonFix";
7
+ import BannersApi from "../../api-services/banners-api.service";
8
+ import { Banner } from "../../interfaces/banner.interface";
9
+
10
+ export default class Homepage extends React.Component {
11
+ api = new BannersApi;
12
+
13
+ state = {
14
+ banners: []
15
+ }
16
+
17
+
18
+ componentDidMount() {
19
+ this.api.getBanners()
20
+ .then(bannersData => {
21
+ this.setState({banners: bannersData})
22
+ })
23
+ .catch(error => {
24
+ notification.error({ message: 'An error occured while loading banners.'});
25
+ });
26
+ }
27
+
28
+ render(){
29
+ const { banners } = this.state;
30
+
31
+ return (
32
+ <div id="homepage">
33
+
34
+ <SearchBarWidget/>
35
+
36
+ <div id="homepage_banner">
37
+ <Carousel autoplay arrows infinite={banners.length>1}
38
+ prevArrow={
39
+ <SlickButtonFix>
40
+ <img src="/iceholidays-assets/images/banner_arrow.png"/>
41
+ </SlickButtonFix>
42
+ }
43
+ nextArrow={
44
+ <SlickButtonFix>
45
+ <img src="/iceholidays-assets/images/banner_arrow.png" className="flip"/>
46
+ </SlickButtonFix>
47
+ }
48
+ >
49
+ {
50
+ banners.map((banner:Banner, index)=>{
51
+ return <a key={index} href={banner.url} target={banner.target} rel="nofollow">
52
+ <picture>
53
+ <source media="(min-width: 600px)" srcSet={banner.desktopImage} />
54
+ <source media="(max-width: 599px)" srcSet={banner.mobileImage} />
55
+ <img src={banner.desktopImage} alt={banner.title} />
56
+ </picture>
57
+ </a>
58
+ })
59
+ }
60
+ </Carousel>
61
+ </div>
62
+
63
+ <Destinations/>
64
+ <Testimonials/>
65
+
66
+ <div id="about-signature-tours" style={{background: "url('/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%)"}}>
67
+ <div>
68
+ <h1>ABOUT SIGNATURE TOURS</h1>
69
+ <p>
70
+ 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.
71
+ </p>
72
+ <img src="/iceholidays-assets/images/Group_71.png"/>
73
+ <div id="about-st-feature">
74
+ <div style={{maxWidth: "449px"}}>
75
+ <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>
76
+ </div>
77
+ <div id="feature">
78
+ <img src="/iceholidays-assets/images/feature.jpg"/>
79
+ <div id="st-brand">
80
+ <span>Signature Tours</span>
81
+ <img src="/iceholidays-assets/images/logomark.png" id="st-logo"/>
82
+ </div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+ </div>
88
+ )
89
+ }
90
+ }
@@ -0,0 +1,246 @@
1
+ import React, { Component } from "react";
2
+ import { Badge, Breadcrumb, Button, Flex, Modal, Space, Select, Row, Col, notification } from "antd";
3
+ import FilterPills from "../widgets/FilterPills";
4
+ import { Link, useSearchParams } from "react-router-dom";
5
+ import { mdiDotsHorizontalCircleOutline, mdiFileDownload, mdiFilterVariant, mdiInformationOutline, mdiMapMarkerOutline } from "@mdi/js";
6
+ import Icon from "@mdi/react";
7
+ import Headline from "../components/shared/Headline";
8
+ import LocationsApi from "../../api-services/locations-api.service";
9
+ import { Country } from "../../interfaces/country.interface";
10
+ import SeriesApi from "../../api-services/series-api.service";
11
+ import { Itinerary } from "../../interfaces/itinerary.interface";
12
+
13
+ import createDOMPurify from 'dompurify'
14
+
15
+ const DOMPurify = createDOMPurify(window)
16
+
17
+
18
+ const bannerPath = '/iceholidays-assets/images/china_listings_cover.png';
19
+ const breadcrumbs = [
20
+ { title: 'Home' },
21
+ { title: 'China' },
22
+ { title: 'Chongqing' }
23
+ ]
24
+
25
+ const legends = [
26
+ { id: "guaranteed", label: "Guaranteed", color: "#DCB062" },
27
+ { id: "selling-fast", label: "Selling Fast", color: "#23D1C0" }
28
+ ]
29
+
30
+ const getColorByType = (type:string) => {
31
+ var legend = legends.find(l => l.id == type);
32
+ return legend ? legend.color : "#000000";
33
+ }
34
+
35
+ function withSearchParams(Component) {
36
+ return props => <Component {...props} searchParams={useSearchParams()} />;
37
+ }
38
+
39
+ class ListingPage extends React.Component <{searchParams}> {
40
+ locationsApi = new LocationsApi;
41
+ seriesApi = new SeriesApi;
42
+
43
+ state = {
44
+ countries: [],
45
+ selectedCountry: {cities: []},
46
+ keyword: "",
47
+ itineraries: [],
48
+ setIsModalOpen: false,
49
+ setIsDateModalOpen: false,
50
+ descriptionData: "",
51
+ departureDatesData: []
52
+ }
53
+
54
+ constructor(props:any){
55
+ super(props);
56
+ }
57
+
58
+ componentDidMount() {
59
+ this.locationsApi.getCountries()
60
+ .then(locationsData => {
61
+ this.setState({countries: locationsData, selectedCountry: locationsData[0]})
62
+ })
63
+ .catch(error => {
64
+ notification.error({ message: 'An error occured while loading countries.'});
65
+ });
66
+
67
+ const [searchParams] = this.props.searchParams;
68
+ const keyword = searchParams.get('keyword');
69
+ this.setState({keyword: keyword})
70
+
71
+ this.getItineraries({keyword});
72
+ }
73
+
74
+ getItineraries(searchParams){
75
+ this.seriesApi.getItineraries(searchParams)
76
+ .then(itinerariesData => {
77
+ this.setState({itineraries: itinerariesData})
78
+ })
79
+ .catch(error => {
80
+ notification.error({ message: 'An error occured while loading itineraries.'});
81
+ });
82
+ }
83
+
84
+ showModal(description){
85
+ this.setState({setIsModalOpen:true, descriptionData: description});
86
+ }
87
+
88
+ closeModal() {
89
+ this.setState({setIsModalOpen:false});
90
+ }
91
+
92
+ showDatesModal(dates) {
93
+ this.setState({setIsDateModalOpen:true, departureDatesData: dates});
94
+ }
95
+
96
+ closeDatesModalatesModal(){
97
+ this.setState({setIsDateModalOpen:false});
98
+ }
99
+
100
+ selectCountry(country){
101
+ const selectedCountry = this.state.countries.find((c:Country) => c.id == country);
102
+ if(selectedCountry){
103
+ this.setState({selectedCountry})
104
+ }
105
+ }
106
+
107
+
108
+ render(){
109
+ const {countries, selectedCountry, keyword, itineraries, descriptionData, departureDatesData } = this.state;
110
+
111
+ return <div id="listing-page">
112
+
113
+ <Headline bannerImage={bannerPath} breadcrumbs={breadcrumbs}>
114
+ <div id="listing-page_header">
115
+ <Flex justify="space-between" align="center">
116
+ <h1>{keyword}</h1>
117
+ <Select
118
+ className="country-filter"
119
+ suffixIcon={<Icon path={mdiFilterVariant} size={1} />}
120
+ defaultValue="china"
121
+ value={selectedCountry["name"]}
122
+ options={countries}
123
+ fieldNames={{label: 'name', value: 'id'}}
124
+ dropdownStyle={{padding: "15px 10px 15px 20px", borderRadius: "20px"}}
125
+ optionRender={(option) => (
126
+ <div className="country-filter_option"> {option.label} </div>
127
+ )}
128
+ onChange={(country)=>this.selectCountry(country)}
129
+ />
130
+ </Flex>
131
+ </div>
132
+ </Headline>
133
+
134
+ <div id="listing-page_body">
135
+ <Space size={12} direction="vertical">
136
+ <FilterPills items={selectedCountry.cities} label="name" initialValue={{keyword}} selectFilter={(selected) => this.getItineraries(selected)}></FilterPills>
137
+ </Space>
138
+
139
+ <div id="legends">
140
+ <Space size={17}>
141
+ {
142
+ legends.map(legend => <Badge key={legend.id} color={legend.color} text={legend.label} />)
143
+ }
144
+ </Space>
145
+ </div>
146
+
147
+ <div id="tours">
148
+ <Flex vertical gap={30}>
149
+ {
150
+ itineraries.map((itinerary:Itinerary) => {
151
+ const departureDates = itinerary.departureDate.map(ddate => {
152
+ return {
153
+ type: itinerary.guranteedDepartureDates.includes(ddate) ? "guaranteed" : "",
154
+ date: ddate
155
+ }
156
+ });
157
+
158
+ return (
159
+ <Row className="tour" wrap={false}>
160
+ <Col flex="300px">
161
+ <div className="tour_image"> <img src={itinerary.images[0]}/> </div>
162
+ </Col>
163
+ <Col flex="auto">
164
+ <div className="tour_details">
165
+ <Space size={20} direction="vertical" style={{ display: 'flex' }}>
166
+ <Space size={10} direction="vertical">
167
+ <div className="tour_details_title"> {itinerary.caption} </div>
168
+ <div className="tour_details_subtitle"> {itinerary.otherCaption} </div>
169
+ <div className="tour_details_info">
170
+ <span onClick={()=>this.showModal(itinerary.description)}><Icon path={mdiInformationOutline} size={1}/></span>
171
+ </div>
172
+ <div>
173
+ <span className="tour_details_country"> <Icon path={mdiMapMarkerOutline} size="18px" /> {itinerary.country} </span>
174
+ <span className="tour_details_code"> <Icon path={mdiFileDownload} size="18px" /> {itinerary.code} </span>
175
+ </div>
176
+ </Space>
177
+ <div className="tour_details_dates">
178
+ <Space size={6} direction="vertical" style={{ display: 'flex' }}>
179
+ <label>Departure Date(s)</label>
180
+ <div className="date-selector">
181
+ {
182
+ departureDates && departureDates.map(ddate => <span style={{borderColor: getColorByType(ddate.type)}}> {ddate.date} </span>)
183
+ }
184
+ {
185
+ departureDates.length >= 9 && <div className="show-all-dates" onClick={()=>this.showDatesModal(departureDates)}> <Icon path={mdiDotsHorizontalCircleOutline} size="15px" /> Show All </div>
186
+ }
187
+ </div>
188
+ </Space>
189
+ </div>
190
+ </Space>
191
+ </div>
192
+ </Col>
193
+ <Col flex="210px">
194
+ <div className="tour_pricing">
195
+ <Space size={20} direction="vertical" align="center">
196
+ <Flex vertical>
197
+ <span>From</span>
198
+ <span className="price">{itinerary.priceCurrency} {itinerary.price}</span>
199
+ <span>All In</span>
200
+ </Flex>
201
+ <Link className="select-tour" to="/app/show">Select</Link>
202
+ </Space>
203
+ </div>
204
+ </Col>
205
+ </Row>
206
+ )
207
+ })
208
+ }
209
+ </Flex>
210
+ </div>
211
+ </div>
212
+
213
+
214
+ <Modal title="Description" open={this.state.setIsModalOpen} onCancel={()=>this.closeModal()} footer={null} width={1000} centered className="tour_details_description">
215
+ <div className="pre-wrap">{ <span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(descriptionData) }} /> }</div>
216
+ </Modal>
217
+
218
+ <Modal title={
219
+ <Flex justify="space-between" align="center">
220
+ <div className="custom-modal-header-title"> Departure Date(s) </div>
221
+ <div className="custom-modal-header-legends">
222
+ <Space size={17}>
223
+ {
224
+ legends.map(legend => <Badge key={legend.id} color={legend.color} text={legend.label} />)
225
+ }
226
+ </Space>
227
+ </div>
228
+ </Flex>
229
+ } footer={
230
+ <Button>Confirm</Button>
231
+ }
232
+ open={this.state.setIsDateModalOpen} onCancel={()=>this.closeDatesModalatesModal()} width={940} centered className="tour_details_all_dates">
233
+ <div className="date-selector">
234
+ <Space size={8} wrap>
235
+ {
236
+ departureDatesData && departureDatesData.map((ddate:any) => <span style={{borderColor: getColorByType(ddate.type)}}> {ddate.date} </span>)
237
+ }
238
+ </Space>
239
+ </div>
240
+ </Modal>
241
+ </div>
242
+ }
243
+ }
244
+
245
+
246
+ export default withSearchParams(ListingPage);