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.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/iceholidays/frontend/application.sass.scss +1588 -0
- data/app/assets/stylesheets/iceholidays/frontend/common.scss +233 -0
- data/app/assets/stylesheets/iceholidays/frontend/layout.scss +188 -0
- data/app/assets/stylesheets/iceholidays/frontend/utils/_antd_overrides.scss +107 -0
- data/app/assets/stylesheets/iceholidays/frontend/utils/_variables.scss +3 -0
- data/app/assets/stylesheets/iceholidays/frontend/widgets/filter_pills.scss +45 -0
- data/app/assets/stylesheets/iceholidays/frontend/widgets/search_bar.scss +112 -0
- data/app/controllers/iceholidays/frontend/site_controller.rb +32 -0
- data/app/javascript/api-services/banners-api.service.ts +28 -0
- data/app/javascript/api-services/locations-api.service.ts +49 -0
- data/app/javascript/api-services/search-api.service.ts +16 -0
- data/app/javascript/api-services/series-api.service.ts +44 -0
- data/app/javascript/api-services/testimonials-api.service.ts +27 -0
- data/app/javascript/interfaces/banner.interface.ts +10 -0
- data/app/javascript/interfaces/country.interface.ts +18 -0
- data/app/javascript/interfaces/itinerary.interface.ts +18 -0
- data/app/javascript/interfaces/testimonial.interface.ts +6 -0
- data/app/javascript/react/App.tsx +32 -0
- data/app/javascript/react/components/Destinations.tsx +72 -141
- data/app/javascript/react/components/Testimonials.tsx +68 -61
- data/app/javascript/react/components/shared/Headline.tsx +29 -0
- data/app/javascript/react/components/shared/Postcard.tsx +31 -0
- data/app/javascript/react/components/shared/RibbonSection.tsx +21 -0
- data/app/javascript/react/index.js +3 -5
- data/app/javascript/react/layouts/MainFooter.tsx +72 -0
- data/app/javascript/react/layouts/MainHeader.tsx +45 -0
- data/app/javascript/react/layouts/MainLayout.tsx +21 -0
- data/app/javascript/react/pages/AboutUsPage.tsx +95 -0
- data/app/javascript/react/pages/BlogPage.tsx +79 -0
- data/app/javascript/react/pages/BlogShowPage.tsx +43 -0
- data/app/javascript/react/pages/ContactAgentsPage.tsx +16 -0
- data/app/javascript/react/pages/ContactUsPage.tsx +122 -0
- data/app/javascript/react/pages/CountriesPage.tsx +62 -0
- data/app/javascript/react/pages/Homepage.tsx +90 -0
- data/app/javascript/react/pages/ListingPage.tsx +246 -0
- data/app/javascript/react/pages/ShowPage.tsx +397 -0
- data/app/javascript/react/widgets/FilterPills.tsx +77 -0
- data/app/javascript/react/widgets/SearchBarWidget.tsx +42 -0
- data/app/views/iceholidays/frontend/site/index.html.erb +1 -24
- data/app/views/layouts/iceholidays/frontend/application.html.erb +2 -6
- data/config/routes.rb +9 -0
- data/lib/iceholidays/frontend/version.rb +1 -1
- data/public/iceholidays-assets/application.css +1873 -0
- data/public/iceholidays-assets/application.js +225 -651
- data/public/iceholidays-assets/application.js.map +4 -4
- data/public/iceholidays-assets/images/8D7N.png +0 -0
- data/public/iceholidays-assets/images/about_us.png +0 -0
- data/public/iceholidays-assets/images/about_us2.png +0 -0
- data/public/iceholidays-assets/images/blog.png +0 -0
- data/public/iceholidays-assets/images/blog1.png +0 -0
- data/public/iceholidays-assets/images/certificate1.png +0 -0
- data/public/iceholidays-assets/images/certificate2.png +0 -0
- data/public/iceholidays-assets/images/china_listings_cover.png +0 -0
- data/public/iceholidays-assets/images/china_southern_airlines.png +0 -0
- data/public/iceholidays-assets/images/china_southern_airlines_icon.png +0 -0
- data/public/iceholidays-assets/images/competitiveness.png +0 -0
- data/public/iceholidays-assets/images/contact_agents.png +0 -0
- data/public/iceholidays-assets/images/contact_us.png +0 -0
- data/public/iceholidays-assets/images/contact_us_form.png +0 -0
- data/public/iceholidays-assets/images/ethical.png +0 -0
- data/public/iceholidays-assets/images/hw_logo.png +0 -0
- data/public/iceholidays-assets/images/innovative.png +0 -0
- data/public/iceholidays-assets/images/plane.png +0 -0
- data/public/iceholidays-assets/images/social/ico_fb.png +0 -0
- data/public/iceholidays-assets/images/social/ico_ig.png +0 -0
- data/public/iceholidays-assets/images/social/ico_twitter.png +0 -0
- data/public/iceholidays-assets/images/social/ico_yt.png +0 -0
- data/public/iceholidays-assets/images/social.png +0 -0
- data/public/iceholidays-assets/images/tour1.png +0 -0
- metadata +61 -49
- data/app/assets/stylesheets/iceholidays/frontend/application.scss +0 -904
- data/app/javascript/react/components/Homepage.tsx +0 -15
- data/app/javascript/react/components/HomepageBanner.tsx +0 -62
- data/app/views/layouts/iceholidays/frontend/shared/_footer.html.erb +0 -42
- data/app/views/layouts/iceholidays/frontend/shared/_header.html.erb +0 -20
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { Banner } from "../interfaces/banner.interface";
|
|
3
|
+
|
|
4
|
+
class BannersApi {
|
|
5
|
+
|
|
6
|
+
getBanners(): Promise<Banner[]> {
|
|
7
|
+
const apiUrl = "/api/v1/banner";
|
|
8
|
+
|
|
9
|
+
return axios.get(apiUrl)
|
|
10
|
+
.then(response => {
|
|
11
|
+
const banners = response.data.data.attributes.slides.map(slide => {
|
|
12
|
+
const banner:Banner = {
|
|
13
|
+
title: slide.title,
|
|
14
|
+
url: slide.url,
|
|
15
|
+
target: slide.target,
|
|
16
|
+
sortOrder: slide.sort_order,
|
|
17
|
+
desktopImage: slide.image.desktop,
|
|
18
|
+
mobileImage: slide.image.mobile
|
|
19
|
+
}
|
|
20
|
+
return banner;
|
|
21
|
+
})
|
|
22
|
+
return Promise.resolve(banners);
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default BannersApi;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { City, Country } from "../interfaces/country.interface";
|
|
3
|
+
|
|
4
|
+
class LocationsApi {
|
|
5
|
+
|
|
6
|
+
getCountries(): Promise<Country[]> {
|
|
7
|
+
const apiUrl = "/api/v1/locations";
|
|
8
|
+
|
|
9
|
+
return axios.get(apiUrl)
|
|
10
|
+
.then(response => {
|
|
11
|
+
const countries = response.data.map(locationData => {
|
|
12
|
+
const countryData = locationData.data.attributes;
|
|
13
|
+
const citiesData = countryData.children.map(childrenData => this.mapCity(childrenData.data.attributes));
|
|
14
|
+
|
|
15
|
+
const allCities = [...citiesData];
|
|
16
|
+
const citiesWithImages = allCities.filter(x => x.cover != null);
|
|
17
|
+
const topCities = citiesWithImages.splice(0,4);
|
|
18
|
+
const highlights = citiesWithImages.splice(0,5);
|
|
19
|
+
|
|
20
|
+
const country:Country = {
|
|
21
|
+
id: countryData.id,
|
|
22
|
+
name: countryData.name,
|
|
23
|
+
cover: countryData.cover,
|
|
24
|
+
tourCount: countryData.itineraries,
|
|
25
|
+
cities: allCities,
|
|
26
|
+
topCities: topCities,
|
|
27
|
+
highlights: highlights,
|
|
28
|
+
tags: allCities.filter(city => !topCities.includes(city) && !highlights.includes(city)),
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return country;
|
|
32
|
+
})
|
|
33
|
+
return Promise.resolve(countries);
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
private mapCity(cityData): City{
|
|
38
|
+
const city:City = {
|
|
39
|
+
id: cityData.id,
|
|
40
|
+
name: cityData.name,
|
|
41
|
+
cover: cityData.cover,
|
|
42
|
+
tourCount: cityData.itineraries
|
|
43
|
+
};
|
|
44
|
+
return city;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default LocationsApi;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
|
|
3
|
+
class SearchApi {
|
|
4
|
+
|
|
5
|
+
search(keyword:string, month?:number): Promise<any[]> {
|
|
6
|
+
const apiUrl = `/api/v1/series?keyword=${keyword}${month && `&month=${month}`}&type=series`;
|
|
7
|
+
|
|
8
|
+
return axios.get(apiUrl)
|
|
9
|
+
.then(response => {
|
|
10
|
+
return Promise.resolve(response.data);
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default SearchApi;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { Itinerary } from "../interfaces/itinerary.interface";
|
|
3
|
+
|
|
4
|
+
interface SearchParams {
|
|
5
|
+
keyword: string;
|
|
6
|
+
year?: string;
|
|
7
|
+
month?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
class SeriesApi {
|
|
11
|
+
|
|
12
|
+
getItineraries(searchParams: SearchParams): Promise<Itinerary[]> {
|
|
13
|
+
const apiUrl = `/api/v1/series?keyword=${searchParams.keyword}&type=series`;
|
|
14
|
+
|
|
15
|
+
return axios.get(apiUrl)
|
|
16
|
+
.then(response => {
|
|
17
|
+
const itineraries:Itinerary[] = response.data.itineraries.map(itineraryData => {
|
|
18
|
+
const itinerary:Itinerary = {
|
|
19
|
+
id: itineraryData.id,
|
|
20
|
+
description: itineraryData.description,
|
|
21
|
+
caption: itineraryData.caption,
|
|
22
|
+
otherCaption: itineraryData.otherCaption,
|
|
23
|
+
code: itineraryData.code,
|
|
24
|
+
category: itineraryData.category,
|
|
25
|
+
country: itineraryData.country,
|
|
26
|
+
price: itineraryData.price,
|
|
27
|
+
images: itineraryData.images,
|
|
28
|
+
priceCurrency: itineraryData.price_currency,
|
|
29
|
+
departureDate: itineraryData.departure_date,
|
|
30
|
+
guranteedDepartureDates: itineraryData.guranteed_departure_dates,
|
|
31
|
+
almostGuaranteedDepartureDates: itineraryData.almost_guaranteed_departure_dates,
|
|
32
|
+
departureLocations: itineraryData.departure_locations,
|
|
33
|
+
totalItineraries: itineraryData.total_itineraries,
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return itinerary;
|
|
37
|
+
})
|
|
38
|
+
return Promise.resolve(itineraries);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export default SeriesApi;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { Testimonial } from "../interfaces/testimonial.interface";
|
|
3
|
+
|
|
4
|
+
class TestimonialsApi {
|
|
5
|
+
|
|
6
|
+
getTestimonials(): Promise<Testimonial[]> {
|
|
7
|
+
const apiUrl = "/api/v1/testimonials";
|
|
8
|
+
|
|
9
|
+
return axios.get(apiUrl)
|
|
10
|
+
.then(response => {
|
|
11
|
+
const testimonials = response.data.data.map(responseData => {
|
|
12
|
+
const testimonialData = responseData.attributes;
|
|
13
|
+
const testimonial:Testimonial = {
|
|
14
|
+
id: testimonialData.id,
|
|
15
|
+
tour: testimonialData.tour,
|
|
16
|
+
author: testimonialData.author,
|
|
17
|
+
comment: testimonialData.body,
|
|
18
|
+
}
|
|
19
|
+
return testimonial;
|
|
20
|
+
})
|
|
21
|
+
return Promise.resolve(testimonials);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export default TestimonialsApi;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface Country extends Location {
|
|
2
|
+
cities:City[];
|
|
3
|
+
topCities:City[];
|
|
4
|
+
highlights:City[];
|
|
5
|
+
tags:City[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface City extends Location {
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
interface Location {
|
|
13
|
+
id:number;
|
|
14
|
+
cover:string;
|
|
15
|
+
name:string;
|
|
16
|
+
tourCount:number;
|
|
17
|
+
|
|
18
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface Itinerary {
|
|
2
|
+
id: number;
|
|
3
|
+
description: string;
|
|
4
|
+
code: string;
|
|
5
|
+
fileUrl?: string;
|
|
6
|
+
category: string;
|
|
7
|
+
caption: string;
|
|
8
|
+
otherCaption: string;
|
|
9
|
+
country: string;
|
|
10
|
+
price: string;
|
|
11
|
+
departureDate: string[];
|
|
12
|
+
guranteedDepartureDates: string[];
|
|
13
|
+
almostGuaranteedDepartureDates: string[];
|
|
14
|
+
departureLocations: string[];
|
|
15
|
+
images: string[];
|
|
16
|
+
priceCurrency: string;
|
|
17
|
+
totalItineraries: number;
|
|
18
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { BrowserRouter, Routes, Route } from "react-router-dom";
|
|
3
|
+
import Homepage from "./pages/Homepage";
|
|
4
|
+
import ListingPage from "./pages/ListingPage";
|
|
5
|
+
import ShowPage from "./pages/ShowPage";
|
|
6
|
+
import MainLayout from "./layouts/MainLayout";
|
|
7
|
+
import AboutUsPage from "./pages/AboutUsPage";
|
|
8
|
+
import CountriesPage from "./pages/CountriesPage";
|
|
9
|
+
import BlogPage from "./pages/BlogPage";
|
|
10
|
+
import ContactAgentsPage from "./pages/ContactAgentsPage";
|
|
11
|
+
import ContactUsPage from "./pages/ContactUsPage";
|
|
12
|
+
import BlogShowPage from "./pages/BlogShowPage";
|
|
13
|
+
|
|
14
|
+
export default function App() {
|
|
15
|
+
return (
|
|
16
|
+
<BrowserRouter>
|
|
17
|
+
<Routes>
|
|
18
|
+
<Route path="/app" element={<MainLayout />}>
|
|
19
|
+
<Route index element={<Homepage />} />
|
|
20
|
+
<Route path="/app/listing" element={<ListingPage />} />
|
|
21
|
+
<Route path="/app/show" element={<ShowPage />} />
|
|
22
|
+
<Route path="/app/about-us" element={<AboutUsPage />} />
|
|
23
|
+
<Route path="/app/countries" element={<CountriesPage />} />
|
|
24
|
+
<Route path="/app/blog" element={<BlogPage />} />
|
|
25
|
+
<Route path="/app/blog/1" element={<BlogShowPage />} />
|
|
26
|
+
<Route path="/app/contact-agents" element={<ContactAgentsPage />} />
|
|
27
|
+
<Route path="/app/contact-us" element={<ContactUsPage />} />
|
|
28
|
+
</Route>
|
|
29
|
+
</Routes>
|
|
30
|
+
</BrowserRouter>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
@@ -1,138 +1,69 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
3
|
-
import { Tabs, TabsProps } from "antd";
|
|
1
|
+
import { notification, Tabs, TabsProps } from "antd";
|
|
4
2
|
import React from "react";
|
|
3
|
+
import LocationsApi from "../../api-services/locations-api.service";
|
|
4
|
+
import { Country } from "../../interfaces/country.interface";
|
|
5
|
+
import Postcard from "./shared/Postcard";
|
|
6
|
+
import { mdiMenuRight } from "@mdi/js";
|
|
7
|
+
import Icon from "@mdi/react";
|
|
8
|
+
import tour from "antd/es/tour";
|
|
9
|
+
import { Link } from "react-router-dom";
|
|
5
10
|
|
|
6
|
-
const countries = [
|
|
7
|
-
{
|
|
8
|
-
name: "Africa",
|
|
9
|
-
image: "iceholidays-assets/images/africa.png",
|
|
10
|
-
topCities: [{
|
|
11
|
-
name: "Kenya",
|
|
12
|
-
image: "iceholidays-assets/images/kenya.png",
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
name: "South Africa",
|
|
16
|
-
image: "iceholidays-assets/images/southafrica.png",
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
name: "Tanzania",
|
|
20
|
-
image: "iceholidays-assets/images/tanzania.png",
|
|
21
|
-
},
|
|
22
|
-
{
|
|
23
|
-
name: "Uaganda",
|
|
24
|
-
image: "iceholidays-assets/images/uganda.png",
|
|
25
|
-
}]
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
name: "China",
|
|
29
|
-
image: "iceholidays-assets/images/china.png",
|
|
30
|
-
topCities: [{
|
|
31
|
-
name: "Chong Qing",
|
|
32
|
-
image: "iceholidays-assets/images/china.png",
|
|
33
|
-
},
|
|
34
|
-
{
|
|
35
|
-
name: "Guangzhou",
|
|
36
|
-
image: "iceholidays-assets/images/guangzhou.png",
|
|
37
|
-
},
|
|
38
|
-
{
|
|
39
|
-
name: "Guilin",
|
|
40
|
-
image: "iceholidays-assets/images/guilin.png",
|
|
41
|
-
},
|
|
42
|
-
{
|
|
43
|
-
name: "Hongkong",
|
|
44
|
-
image: "iceholidays-assets/images/hongkong.png",
|
|
45
|
-
}],
|
|
46
|
-
moreCities: [
|
|
47
|
-
"Leizhou", "Ningxia", "Qinghai", "Shanghai", "Tibet", "Xiamen", "Yunnan"
|
|
48
|
-
],
|
|
49
|
-
tours: [
|
|
50
|
-
{
|
|
51
|
-
name: "Harbin",
|
|
52
|
-
image: "iceholidays-assets/images/harbin.png",
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
name: "Inner Mongolia",
|
|
56
|
-
image: "iceholidays-assets/images/inner_mongolia.png",
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
name: "Jiangxi",
|
|
60
|
-
image: "iceholidays-assets/images/jiangxi.png",
|
|
61
|
-
},
|
|
62
|
-
{
|
|
63
|
-
name: "Kun Ming",
|
|
64
|
-
image: "iceholidays-assets/images/kunming.png",
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
name: "Silk Road",
|
|
68
|
-
image: "iceholidays-assets/images/slikroad.png",
|
|
69
|
-
}
|
|
70
|
-
]
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
name: "Europe"
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
name: "Exotic"
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
name: "japan"
|
|
80
|
-
},
|
|
81
|
-
{
|
|
82
|
-
name: "korea"
|
|
83
|
-
},
|
|
84
|
-
{
|
|
85
|
-
name: "maldives"
|
|
86
|
-
},
|
|
87
|
-
{
|
|
88
|
-
name: "taiwan"
|
|
89
|
-
},
|
|
90
|
-
]
|
|
91
11
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
12
|
+
export default class Destinations extends React.Component {
|
|
13
|
+
api = new LocationsApi;
|
|
14
|
+
|
|
15
|
+
state = {
|
|
16
|
+
countries: []
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
componentDidMount() {
|
|
20
|
+
this.api.getCountries()
|
|
21
|
+
.then(locationsData => {
|
|
22
|
+
this.setState({countries: locationsData})
|
|
23
|
+
})
|
|
24
|
+
.catch(error => {
|
|
25
|
+
notification.error({ message: 'An error occured while loading countries.'});
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
render(){
|
|
30
|
+
const pluralize = (count: number, noun: string, suffix = 's') =>
|
|
31
|
+
`${count} ${noun}${count > 1 ? suffix : ''}`;
|
|
32
|
+
|
|
33
|
+
const listingLink = (keyword: string) => {
|
|
34
|
+
return `/app/listing?keyword=${keyword}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
var items:TabsProps["items"] = this.state.countries.map((country:Country, index)=>{
|
|
38
|
+
return {
|
|
39
|
+
key: index.toString(),
|
|
40
|
+
label: country.name,
|
|
41
|
+
children: (
|
|
99
42
|
<div>
|
|
100
|
-
<
|
|
101
|
-
<img className="country-image"
|
|
102
|
-
src={`${country.image}`} />
|
|
43
|
+
<Link to={listingLink(country.name)} className="country">
|
|
44
|
+
<img className="country-image" src={`${country.cover}`} />
|
|
103
45
|
<h1 className="country-name"> {country.name} </h1>
|
|
104
|
-
<img className="bottom-logo" src="iceholidays-assets/images/Frame71.png"></img>
|
|
105
|
-
</
|
|
46
|
+
<img className="bottom-logo" src="/iceholidays-assets/images/Frame71.png"></img>
|
|
47
|
+
</Link>
|
|
106
48
|
{
|
|
107
|
-
country.topCities?.length &&
|
|
49
|
+
country.topCities?.length > 0 &&
|
|
108
50
|
<div className="top-cities">
|
|
109
51
|
<h2>Top Cities</h2>
|
|
110
|
-
<div
|
|
52
|
+
<div className="postcards">
|
|
111
53
|
{
|
|
112
|
-
country.topCities?.map((city, index)
|
|
113
|
-
return (
|
|
114
|
-
<div key={index} className="city-cover">
|
|
115
|
-
<img src={`${city.image}`}/>
|
|
116
|
-
<div className="city-details">
|
|
117
|
-
<span className="city-name">{city.name}</span>
|
|
118
|
-
<span className="tour-count">1 Tour <FontAwesomeIcon icon={faCaretRight}/> </span>
|
|
119
|
-
</div>
|
|
120
|
-
</div>
|
|
121
|
-
)
|
|
122
|
-
})
|
|
54
|
+
country.topCities?.map((city, index)=><Postcard key={index} link={listingLink(city.name)} image={city.cover} name={city.name} tourCount={city.tourCount}/>)
|
|
123
55
|
}
|
|
124
56
|
</div>
|
|
125
57
|
</div>
|
|
126
58
|
}
|
|
127
|
-
|
|
128
59
|
{
|
|
129
|
-
country.
|
|
60
|
+
country.tags?.length > 0 &&
|
|
130
61
|
<div className="more-cities">
|
|
131
62
|
<h2>Browse more {country.name} destination</h2>
|
|
132
63
|
<div className="other-destinations">
|
|
133
64
|
{
|
|
134
|
-
country.
|
|
135
|
-
return <
|
|
65
|
+
country.tags?.map((city, index)=>{
|
|
66
|
+
return <Link key={index} to={listingLink(city.name)}> {city.name} </Link>
|
|
136
67
|
})
|
|
137
68
|
}
|
|
138
69
|
</div>
|
|
@@ -140,37 +71,37 @@ function Destinations(){
|
|
|
140
71
|
}
|
|
141
72
|
|
|
142
73
|
{
|
|
143
|
-
country.
|
|
144
|
-
<div className="
|
|
74
|
+
country.highlights?.length > 0 &&
|
|
75
|
+
<div className="highlights">
|
|
145
76
|
{
|
|
146
|
-
country.
|
|
147
|
-
return <
|
|
148
|
-
<div className="tour-
|
|
149
|
-
<
|
|
150
|
-
|
|
77
|
+
country.highlights?.map((city, index)=>{
|
|
78
|
+
return <Link key={index} to={listingLink(city.name)}>
|
|
79
|
+
<div className="tour" style={{backgroundImage:`linear-gradient(180deg, rgba(0, 0, 0, 0) 50%, rgba(0, 0, 0, 0.8) 100%), url("${city.cover}")`}}>
|
|
80
|
+
<div className="details">
|
|
81
|
+
<span className="city-name"> {city.name} </span>
|
|
82
|
+
<span className="tour-count"> {pluralize(city.tourCount, "tour")} <Icon path={mdiMenuRight} size={1} /> </span>
|
|
83
|
+
</div>
|
|
151
84
|
</div>
|
|
152
|
-
</
|
|
85
|
+
</Link>
|
|
153
86
|
})
|
|
154
87
|
}
|
|
155
88
|
</div>
|
|
156
89
|
}
|
|
157
90
|
</div>
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
}
|
|
161
|
-
})
|
|
162
|
-
|
|
163
|
-
return (
|
|
164
|
-
<div id="popular-destinations">
|
|
165
|
-
<h1>Explore Popular Destination</h1>
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
})
|
|
166
94
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
type="card"/>
|
|
95
|
+
return (
|
|
96
|
+
<div id="popular-destinations">
|
|
97
|
+
<h1>Explore Popular Destination</h1>
|
|
171
98
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
99
|
+
<Tabs
|
|
100
|
+
defaultActiveKey="1"
|
|
101
|
+
items={items}
|
|
102
|
+
type="card"/>
|
|
175
103
|
|
|
176
|
-
|
|
104
|
+
</div>
|
|
105
|
+
)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -1,19 +1,75 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
|
3
|
-
import { Carousel } from "antd";
|
|
1
|
+
import { Carousel, notification } from "antd";
|
|
4
2
|
import React from "react";
|
|
5
3
|
import SlickButtonFix from "./shared/SlickButtonFix";
|
|
6
|
-
import {
|
|
7
|
-
import
|
|
4
|
+
import { mdiFountainPenTip, mdiMenuLeft, mdiMenuRight } from "@mdi/js";
|
|
5
|
+
import Icon from "@mdi/react";
|
|
6
|
+
import RibbonSection from "./shared/RibbonSection";
|
|
7
|
+
import TestimonialsApi from "../../api-services/testimonials-api.service";
|
|
8
|
+
import { Testimonial } from "../../interfaces/testimonial.interface";
|
|
9
|
+
import createDOMPurify from 'dompurify'
|
|
8
10
|
|
|
11
|
+
const DOMPurify = createDOMPurify(window)
|
|
9
12
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
export default class Testimonials extends React.Component {
|
|
14
|
+
api = new TestimonialsApi;
|
|
15
|
+
|
|
16
|
+
state = {
|
|
17
|
+
reviews: []
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
componentDidMount() {
|
|
21
|
+
this.api.getTestimonials()
|
|
22
|
+
.then(testimonialsData => {
|
|
23
|
+
this.setState({reviews: testimonialsData})
|
|
24
|
+
})
|
|
25
|
+
.catch(error => {
|
|
26
|
+
notification.error({ message: 'An error occured while loading testimonials.'});
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
render(){
|
|
31
|
+
return (
|
|
32
|
+
<RibbonSection title="Testimonial">
|
|
33
|
+
<div id="testimonials">
|
|
34
|
+
<Carousel autoplay arrows dots={false} draggable slidesToScroll= {1} slidesToShow={this.state.reviews.length > 3 ? 3 : this.state.reviews.length}
|
|
35
|
+
prevArrow={
|
|
36
|
+
<SlickButtonFix>
|
|
37
|
+
<Icon path={mdiMenuLeft} size={4} />
|
|
38
|
+
</SlickButtonFix>
|
|
39
|
+
}
|
|
40
|
+
nextArrow={
|
|
41
|
+
<SlickButtonFix>
|
|
42
|
+
<Icon path={mdiMenuRight} size={4} />
|
|
43
|
+
</SlickButtonFix>
|
|
44
|
+
}>
|
|
45
|
+
{
|
|
46
|
+
this.state.reviews.map((review:Testimonial)=>{
|
|
47
|
+
return (
|
|
48
|
+
<div key={review.id} className="testimonial">
|
|
49
|
+
<img src="/iceholidays-assets/images/5star.png" className="stars-icon"/>
|
|
50
|
+
<div className="tour">
|
|
51
|
+
<img src="/iceholidays-assets/images/logomark.png" className="st-logo"/>
|
|
52
|
+
<div>
|
|
53
|
+
<span> {review.tour} </span>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="comment">
|
|
57
|
+
<span dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(review.comment) }} />
|
|
58
|
+
</div>
|
|
59
|
+
<div className="author">
|
|
60
|
+
<Icon path={mdiFountainPenTip} size={1} className="author-icon" />
|
|
61
|
+
<span>{review.author}</span>
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
)
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
</Carousel>
|
|
68
|
+
</div>
|
|
69
|
+
</RibbonSection>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
17
73
|
|
|
18
74
|
const reviews = [
|
|
19
75
|
{
|
|
@@ -37,52 +93,3 @@ const reviews = [
|
|
|
37
93
|
},
|
|
38
94
|
]
|
|
39
95
|
|
|
40
|
-
function Testimonials(){
|
|
41
|
-
return (
|
|
42
|
-
<div id="testimonial">
|
|
43
|
-
<div id="testimonial-header" style={{backgroundImage: "url('iceholidays-assets/images/Layer_1.png')"}}>
|
|
44
|
-
<span>Testimonial</span>
|
|
45
|
-
</div>
|
|
46
|
-
|
|
47
|
-
<div id="testimonials">
|
|
48
|
-
<Carousel autoplay arrows dots={false} draggable slidesToScroll= {1} slidesToShow={3}
|
|
49
|
-
prevArrow={
|
|
50
|
-
<SlickButtonFix>
|
|
51
|
-
<FontAwesomeIcon icon={faCaretLeft} size="4x"/>
|
|
52
|
-
</SlickButtonFix>
|
|
53
|
-
}
|
|
54
|
-
nextArrow={
|
|
55
|
-
<SlickButtonFix>
|
|
56
|
-
<FontAwesomeIcon icon={faCaretRight} size="4x"/>
|
|
57
|
-
</SlickButtonFix>
|
|
58
|
-
}>
|
|
59
|
-
{
|
|
60
|
-
reviews?.map((review)=>{
|
|
61
|
-
return (
|
|
62
|
-
<div key={review.key} className="testimonial">
|
|
63
|
-
<img src="iceholidays-assets/images/5star.png" className="stars-icon"/>
|
|
64
|
-
<div className="itinerary-summary">
|
|
65
|
-
<img src="iceholidays-assets/images/logomark.png" className="st-logo"/>
|
|
66
|
-
<div>
|
|
67
|
-
<span> {review.itinerarySummaryTitle} </span>
|
|
68
|
-
<span> {review.itinerarySummaryDesc} </span>
|
|
69
|
-
</div>
|
|
70
|
-
</div>
|
|
71
|
-
<div className="comment">
|
|
72
|
-
<span>{review.comment}</span>
|
|
73
|
-
</div>
|
|
74
|
-
<div className="customer-name">
|
|
75
|
-
<FontAwesomeIcon icon={faPenNib} className="author-icon" />
|
|
76
|
-
<span>{review.customerName}</span>
|
|
77
|
-
</div>
|
|
78
|
-
</div>
|
|
79
|
-
)
|
|
80
|
-
})
|
|
81
|
-
}
|
|
82
|
-
</Carousel>
|
|
83
|
-
</div>
|
|
84
|
-
</div>
|
|
85
|
-
)
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export default Testimonials;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Breadcrumb } from "antd";
|
|
2
|
+
import React, { JSX } from "react";
|
|
3
|
+
import SearchBarWidget from "../../widgets/SearchBarWidget";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const Headline = (
|
|
7
|
+
props: {
|
|
8
|
+
bannerImage: string;
|
|
9
|
+
title?: string;
|
|
10
|
+
children?: JSX.Element;
|
|
11
|
+
breadcrumbs: any[];
|
|
12
|
+
}
|
|
13
|
+
) => {
|
|
14
|
+
const { bannerImage, title, children, breadcrumbs } = props;
|
|
15
|
+
return (
|
|
16
|
+
<>
|
|
17
|
+
<SearchBarWidget/>
|
|
18
|
+
<div id="headline">
|
|
19
|
+
<div id="headline_banner" style={{backgroundImage: `url(${bannerImage})`}}></div>
|
|
20
|
+
<div id="headline_header">
|
|
21
|
+
{ title ? <h1> {title} </h1> : children}
|
|
22
|
+
</div>
|
|
23
|
+
<Breadcrumb items={breadcrumbs} separator=">"/>
|
|
24
|
+
</div>
|
|
25
|
+
</>
|
|
26
|
+
)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export default Headline;
|