iceholidays-frontend 0.4.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.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/iceholidays/frontend/actiontext.scss +119 -0
- data/app/assets/stylesheets/iceholidays/frontend/application.sass.scss +961 -355
- data/app/assets/stylesheets/iceholidays/frontend/common.scss +156 -72
- data/app/assets/stylesheets/iceholidays/frontend/layout.scss +218 -125
- data/app/assets/stylesheets/iceholidays/frontend/utils/_antd_overrides.scss +22 -7
- data/app/assets/stylesheets/iceholidays/frontend/utils/_variables.scss +2 -1
- data/app/assets/stylesheets/iceholidays/frontend/widgets/filter_pills.scss +19 -12
- data/app/assets/stylesheets/iceholidays/frontend/widgets/search_bar.scss +4 -0
- data/app/controllers/iceholidays/frontend/posts_controller.rb +14 -0
- data/app/javascript/api-services/agents-api.service.ts +33 -0
- data/app/javascript/api-services/locations-api.service.ts +23 -1
- data/app/javascript/api-services/series-api.service.ts +48 -28
- data/app/javascript/interfaces/agent.interface.ts +11 -0
- data/app/javascript/interfaces/country.interface.ts +4 -3
- data/app/javascript/interfaces/itinerary.interface.ts +101 -8
- data/app/javascript/react/App.tsx +1 -1
- data/app/javascript/react/components/Destinations.tsx +30 -20
- data/app/javascript/react/components/PriceDetails.tsx +146 -0
- data/app/javascript/react/components/shared/ContactAgentsForm.tsx +44 -0
- data/app/javascript/react/components/shared/Headline.tsx +2 -1
- data/app/javascript/react/components/shared/LocationDropdown.tsx +34 -0
- data/app/javascript/react/components/shared/{Postcard.tsx → LocationPostcards.tsx} +22 -1
- data/app/javascript/react/layouts/MainFooter.tsx +64 -39
- data/app/javascript/react/layouts/MainHeader.tsx +68 -30
- data/app/javascript/react/pages/AboutUsPage.tsx +6 -6
- data/app/javascript/react/pages/BlogPage.tsx +6 -4
- data/app/javascript/react/pages/ContactAgentsPage.tsx +174 -5
- data/app/javascript/react/pages/ContactUsPage.tsx +5 -5
- data/app/javascript/react/pages/CountriesPage.tsx +3 -8
- data/app/javascript/react/pages/Homepage.tsx +23 -13
- data/app/javascript/react/pages/ListingPage.tsx +192 -146
- data/app/javascript/react/pages/ShowPage.tsx +269 -264
- data/app/javascript/react/widgets/FilterPills.tsx +83 -49
- data/app/javascript/react/widgets/SearchBarWidget.tsx +24 -8
- data/app/views/iceholidays/frontend/posts/index.html.erb +9 -0
- data/app/views/iceholidays/frontend/posts/show.html.erb +2 -0
- data/config/routes.rb +2 -1
- data/lib/iceholidays/frontend/version.rb +1 -1
- data/public/iceholidays-assets/application.css +1190 -425
- data/public/iceholidays-assets/application.js +91 -104
- data/public/iceholidays-assets/application.js.map +4 -4
- data/public/iceholidays-assets/images/about-us_logo_mobile.png +0 -0
- data/public/iceholidays-assets/images/destinations_logo.png +0 -0
- data/public/iceholidays-assets/images/footer-bg_mobile.png +0 -0
- data/public/iceholidays-assets/images/logo_mobile.png +0 -0
- metadata +18 -27
- data/public/iceholidays-assets/images/8D7N.png +0 -0
- data/public/iceholidays-assets/images/Frame71.png +0 -0
- data/public/iceholidays-assets/images/africa.png +0 -0
- data/public/iceholidays-assets/images/banner1.png +0 -0
- data/public/iceholidays-assets/images/banner2.png +0 -0
- data/public/iceholidays-assets/images/china.png +0 -0
- data/public/iceholidays-assets/images/china2.png +0 -0
- data/public/iceholidays-assets/images/chongqing.png +0 -0
- data/public/iceholidays-assets/images/guangzhou.png +0 -0
- data/public/iceholidays-assets/images/guilin.png +0 -0
- data/public/iceholidays-assets/images/harbin.png +0 -0
- data/public/iceholidays-assets/images/hongkong.png +0 -0
- data/public/iceholidays-assets/images/inner_mongolia.png +0 -0
- data/public/iceholidays-assets/images/jiangxi.png +0 -0
- data/public/iceholidays-assets/images/kenya.png +0 -0
- data/public/iceholidays-assets/images/kenya2.png +0 -0
- data/public/iceholidays-assets/images/kunming.png +0 -0
- data/public/iceholidays-assets/images/slikroad.png +0 -0
- data/public/iceholidays-assets/images/southafrica.png +0 -0
- data/public/iceholidays-assets/images/tanzania.png +0 -0
- data/public/iceholidays-assets/images/tour1.png +0 -0
- data/public/iceholidays-assets/images/uganda.png +0 -0
- /data/public/iceholidays-assets/images/{Group_71.png → about-us_logo.png} +0 -0
- /data/public/iceholidays-assets/images/{logo_container.png → logo.png} +0 -0
|
@@ -17,22 +17,17 @@
|
|
|
17
17
|
color: #836848;
|
|
18
18
|
cursor: pointer;
|
|
19
19
|
text-align: center;
|
|
20
|
-
min-width: 150px;
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
// &.month{
|
|
27
|
-
// min-width: 150px;
|
|
28
|
-
// }
|
|
21
|
+
&.default-filter{
|
|
22
|
+
background: #F9E298;
|
|
23
|
+
}
|
|
29
24
|
|
|
30
25
|
span{
|
|
31
26
|
font-family: $font-default;
|
|
32
|
-
font-
|
|
33
|
-
font-
|
|
34
|
-
line-height:
|
|
35
|
-
letter-spacing:
|
|
27
|
+
font-weight: 500;
|
|
28
|
+
font-size: 14px;
|
|
29
|
+
line-height: 21px;
|
|
30
|
+
letter-spacing: 2%;
|
|
36
31
|
text-align: left;
|
|
37
32
|
text-underline-position: from-font;
|
|
38
33
|
text-decoration-skip-ink: none;
|
|
@@ -43,3 +38,15 @@
|
|
|
43
38
|
color: #FFFFFF;
|
|
44
39
|
}
|
|
45
40
|
}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@media only screen and (min-width: 769px) {
|
|
44
|
+
.filter-pill{
|
|
45
|
+
span{
|
|
46
|
+
font-size: 18px;
|
|
47
|
+
font-weight: 400;
|
|
48
|
+
line-height: 27px;
|
|
49
|
+
letter-spacing: 0.05em;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { Agent } from "../interfaces/agent.interface";
|
|
3
|
+
|
|
4
|
+
class AgentsApi
|
|
5
|
+
{
|
|
6
|
+
|
|
7
|
+
getAgents(): Promise<Agent[]> {
|
|
8
|
+
const apiUrl = "/api/v1/users/agents";
|
|
9
|
+
|
|
10
|
+
return axios.get(apiUrl)
|
|
11
|
+
.then(response => {
|
|
12
|
+
const agents = response.data.agents.map(agentData => {
|
|
13
|
+
const agent:Agent = {
|
|
14
|
+
id: agentData.id,
|
|
15
|
+
name: agentData.full_name,
|
|
16
|
+
email: agentData.email,
|
|
17
|
+
image: agentData.image,
|
|
18
|
+
phone: agentData.phone,
|
|
19
|
+
whatsapp: agentData.whatsapp_no,
|
|
20
|
+
address: agentData.address,
|
|
21
|
+
city: agentData.city,
|
|
22
|
+
state: agentData.state,
|
|
23
|
+
}
|
|
24
|
+
return agent;
|
|
25
|
+
})
|
|
26
|
+
return Promise.resolve(agents);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default AgentsApi
|
|
33
|
+
;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
|
-
import { City, Country } from "../interfaces/country.interface";
|
|
2
|
+
import { City, Country, Location } from "../interfaces/country.interface";
|
|
3
3
|
|
|
4
4
|
class LocationsApi {
|
|
5
5
|
|
|
@@ -34,6 +34,28 @@ class LocationsApi {
|
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
getLocation(locationId:number): Promise<Location> {
|
|
38
|
+
const apiUrl = `/api/v1/locations/${locationId}`;
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
return axios.get(apiUrl)
|
|
42
|
+
.then(response => {
|
|
43
|
+
const locationData = response.data.data.attributes;
|
|
44
|
+
const location: Location = {
|
|
45
|
+
id: locationData.id,
|
|
46
|
+
cover: locationData.cover,
|
|
47
|
+
name: locationData.name,
|
|
48
|
+
ancestry: locationData.ancestors.map(a => {
|
|
49
|
+
return {
|
|
50
|
+
id: a.id,
|
|
51
|
+
name: a.name
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
return Promise.resolve(location);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
37
59
|
private mapCity(cityData): City{
|
|
38
60
|
const city:City = {
|
|
39
61
|
id: cityData.id,
|
|
@@ -1,44 +1,64 @@
|
|
|
1
1
|
import axios from "axios";
|
|
2
2
|
import { Itinerary } from "../interfaces/itinerary.interface";
|
|
3
3
|
|
|
4
|
-
interface SearchParams {
|
|
5
|
-
keyword: string;
|
|
6
|
-
year?: string;
|
|
7
|
-
month?: string;
|
|
8
|
-
}
|
|
9
4
|
|
|
10
5
|
class SeriesApi {
|
|
11
6
|
|
|
12
|
-
getItineraries(
|
|
13
|
-
const
|
|
7
|
+
getItineraries(searchParamsObj): Promise<Itinerary[]> {
|
|
8
|
+
const searchParamsQuery = new URLSearchParams(searchParamsObj).toString()
|
|
9
|
+
const apiUrl = `/api/v1/series?${searchParamsQuery}&type=series`;
|
|
14
10
|
|
|
15
11
|
return axios.get(apiUrl)
|
|
16
12
|
.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
|
-
})
|
|
13
|
+
const itineraries:Itinerary[] = response.data.itineraries.map(itineraryData => this.mapItinerary(itineraryData));
|
|
38
14
|
return Promise.resolve(itineraries);
|
|
39
15
|
});
|
|
40
16
|
}
|
|
41
17
|
|
|
18
|
+
|
|
19
|
+
getItinerary(id): Promise<Itinerary>{
|
|
20
|
+
const apiUrl = `/api/v1/series/${id}`;
|
|
21
|
+
|
|
22
|
+
return axios.get(apiUrl)
|
|
23
|
+
.then(response => {
|
|
24
|
+
const itinerary = this.mapItinerary(response.data);
|
|
25
|
+
return Promise.resolve(itinerary);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
private mapItinerary(itineraryData):Itinerary {
|
|
31
|
+
const itinerary:Itinerary = {
|
|
32
|
+
id: itineraryData.id,
|
|
33
|
+
description: itineraryData.description,
|
|
34
|
+
caption: itineraryData.caption,
|
|
35
|
+
otherCaption: itineraryData.otherCaption,
|
|
36
|
+
code: itineraryData.code,
|
|
37
|
+
category: itineraryData.category,
|
|
38
|
+
country: itineraryData.country,
|
|
39
|
+
price: itineraryData.price,
|
|
40
|
+
images: itineraryData.images,
|
|
41
|
+
coverImage: itineraryData.images[0],
|
|
42
|
+
priceCurrency: itineraryData.price_currency,
|
|
43
|
+
departureDate: itineraryData.departure_date,
|
|
44
|
+
guranteedDepartureDates: itineraryData.guranteed_departure_dates,
|
|
45
|
+
almostGuaranteedDepartureDates: itineraryData.almost_guaranteed_departure_dates,
|
|
46
|
+
departureLocations: itineraryData.departure_locations,
|
|
47
|
+
totalItineraries: itineraryData.total_itineraries,
|
|
48
|
+
fileUrl: itineraryData.file_url,
|
|
49
|
+
|
|
50
|
+
autoCancel: itineraryData.auto_cancel,
|
|
51
|
+
ccRate: itineraryData.cc_rate,
|
|
52
|
+
includings: itineraryData.includings,
|
|
53
|
+
maxBookingSeats: itineraryData.max_booking_seats,
|
|
54
|
+
remark: itineraryData.remark,
|
|
55
|
+
tourType: itineraryData.tour_type,
|
|
56
|
+
tours: itineraryData.tours,
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return itinerary;
|
|
60
|
+
}
|
|
61
|
+
|
|
42
62
|
}
|
|
43
63
|
|
|
44
64
|
export default SeriesApi;
|
|
@@ -2,17 +2,110 @@ export interface Itinerary {
|
|
|
2
2
|
id: number;
|
|
3
3
|
description: string;
|
|
4
4
|
code: string;
|
|
5
|
-
fileUrl?: string;
|
|
6
5
|
category: string;
|
|
7
6
|
caption: string;
|
|
8
7
|
otherCaption: string;
|
|
9
8
|
country: string;
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
fileUrl?: string;
|
|
10
|
+
price?: string;
|
|
11
|
+
departureDate?: string[];
|
|
12
|
+
guranteedDepartureDates?: string[];
|
|
13
|
+
almostGuaranteedDepartureDates?: string[];
|
|
14
|
+
departureLocations?: string[];
|
|
15
|
+
coverImage?:string[];
|
|
15
16
|
images: string[];
|
|
16
|
-
priceCurrency
|
|
17
|
-
totalItineraries
|
|
17
|
+
priceCurrency?: string;
|
|
18
|
+
totalItineraries?: number;
|
|
19
|
+
|
|
20
|
+
autoCancel?:boolean;
|
|
21
|
+
ccRate?: number;
|
|
22
|
+
includings?: ItineraryIncludings;
|
|
23
|
+
maxBookingSeats?: number;
|
|
24
|
+
remark?: string;
|
|
25
|
+
tourType?: string;
|
|
26
|
+
tours?: Tour[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface ItineraryIncludings {
|
|
30
|
+
acf: boolean;
|
|
31
|
+
airport_taxes: boolean;
|
|
32
|
+
gratuities: boolean;
|
|
33
|
+
group_departure: boolean;
|
|
34
|
+
hotel: boolean;
|
|
35
|
+
luggage: boolean;
|
|
36
|
+
meal_onboard: boolean;
|
|
37
|
+
tour_leader: boolean;
|
|
38
|
+
wifi: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface Tour {
|
|
42
|
+
id:number;
|
|
43
|
+
addon_others:number;
|
|
44
|
+
allow_customer_pay: boolean;
|
|
45
|
+
arrival_date: string;
|
|
46
|
+
caption: string;
|
|
47
|
+
code: string;
|
|
48
|
+
compulsory_additional_fee: number;
|
|
49
|
+
credit_available: number;
|
|
50
|
+
customer_deposit: number;
|
|
51
|
+
day_before_departure: number;
|
|
52
|
+
departure_date: string;
|
|
53
|
+
departure_location: string;
|
|
54
|
+
deposit: number;
|
|
55
|
+
deposit_date: string;
|
|
56
|
+
dobw_available: any;
|
|
57
|
+
dobw_cor_available: any;
|
|
58
|
+
dobw_ret_available: any;
|
|
59
|
+
extra_deposit: number;
|
|
60
|
+
fare_type: string;
|
|
61
|
+
final_payment_date: string;
|
|
62
|
+
flights: any[];
|
|
63
|
+
fpxb2b_available:any;
|
|
64
|
+
fpxb2c_available:any;
|
|
65
|
+
guaranteed_departure: boolean;
|
|
66
|
+
guaranteed_indicator: string;
|
|
67
|
+
highlight: string;
|
|
68
|
+
insurance: string;
|
|
69
|
+
insurance_rebate: number;
|
|
70
|
+
latest_tour_confirmation: LatestTourConfirmation;
|
|
71
|
+
max_booking_seats: number;
|
|
72
|
+
min_dta: number;
|
|
73
|
+
pay_later: string;
|
|
74
|
+
pbb2_available: string;
|
|
75
|
+
pre_commisions: any[];
|
|
76
|
+
price: number;
|
|
77
|
+
prices: Price[];
|
|
78
|
+
red_luggage_protection: boolean;
|
|
79
|
+
single_supplement_price: string;
|
|
80
|
+
subtract_others: number;
|
|
81
|
+
visa_fee: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface LatestTourConfirmation {
|
|
85
|
+
assembly_time: string;
|
|
86
|
+
confirmation_emergency_contact: string;
|
|
87
|
+
hand_carry_weight: string;
|
|
88
|
+
luggage_quantity: string;
|
|
89
|
+
luggage_weight: string;
|
|
90
|
+
meet_up_point: string;
|
|
91
|
+
tour_guide_chinese_name: string;
|
|
92
|
+
tour_guide_name: string;
|
|
93
|
+
tour_guide_phone: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface Price {
|
|
97
|
+
display_price: string;
|
|
98
|
+
adult: number;
|
|
99
|
+
child_no_bed: number;
|
|
100
|
+
child_twin: number;
|
|
101
|
+
child_with_bed: number;
|
|
102
|
+
dta_adult: number;
|
|
103
|
+
dta_child_no_bed: number;
|
|
104
|
+
dta_child_twin: number;
|
|
105
|
+
dta_child_with_bed: number;
|
|
106
|
+
dta_infant: number;
|
|
107
|
+
dta_single_supplement: number;
|
|
108
|
+
infant: number;
|
|
109
|
+
single_supplement: number;
|
|
110
|
+
type: string;
|
|
18
111
|
}
|
|
@@ -18,7 +18,7 @@ export default function App() {
|
|
|
18
18
|
<Route path="/app" element={<MainLayout />}>
|
|
19
19
|
<Route index element={<Homepage />} />
|
|
20
20
|
<Route path="/app/listing" element={<ListingPage />} />
|
|
21
|
-
<Route path="/app/
|
|
21
|
+
<Route path="/app/itinerary/:id" element={<ShowPage />} />
|
|
22
22
|
<Route path="/app/about-us" element={<AboutUsPage />} />
|
|
23
23
|
<Route path="/app/countries" element={<CountriesPage />} />
|
|
24
24
|
<Route path="/app/blog" element={<BlogPage />} />
|
|
@@ -1,19 +1,20 @@
|
|
|
1
|
-
import { notification, Tabs, TabsProps } from "antd";
|
|
1
|
+
import { notification, Select, Tabs, TabsProps } from "antd";
|
|
2
2
|
import React from "react";
|
|
3
3
|
import LocationsApi from "../../api-services/locations-api.service";
|
|
4
4
|
import { Country } from "../../interfaces/country.interface";
|
|
5
|
-
import
|
|
6
|
-
import { mdiMenuRight } from "@mdi/js";
|
|
5
|
+
import { mdiFilterVariant, mdiMenuRight } from "@mdi/js";
|
|
7
6
|
import Icon from "@mdi/react";
|
|
8
|
-
import tour from "antd/es/tour";
|
|
9
7
|
import { Link } from "react-router-dom";
|
|
8
|
+
import LocationPostcards from "./shared/LocationPostcards";
|
|
9
|
+
import LocationDropdown from "./shared/LocationDropdown";
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
export default class Destinations extends React.Component {
|
|
13
13
|
api = new LocationsApi;
|
|
14
14
|
|
|
15
15
|
state = {
|
|
16
|
-
countries: []
|
|
16
|
+
countries: [],
|
|
17
|
+
activeTab: "1"
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
componentDidMount() {
|
|
@@ -30,30 +31,35 @@ export default class Destinations extends React.Component {
|
|
|
30
31
|
const pluralize = (count: number, noun: string, suffix = 's') =>
|
|
31
32
|
`${count} ${noun}${count > 1 ? suffix : ''}`;
|
|
32
33
|
|
|
33
|
-
const listingLink = (
|
|
34
|
-
return `/app/listing?
|
|
34
|
+
const listingLink = (locationId: number) => {
|
|
35
|
+
return `/app/listing?location_id=${locationId}`;
|
|
35
36
|
}
|
|
36
37
|
|
|
38
|
+
const changeTab = (selectedKey:string) => {
|
|
39
|
+
this.setState({activeTab:selectedKey.toString()})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
|
|
37
43
|
var items:TabsProps["items"] = this.state.countries.map((country:Country, index)=>{
|
|
38
44
|
return {
|
|
39
|
-
key:
|
|
40
|
-
label:
|
|
45
|
+
key: country.id.toString(),
|
|
46
|
+
label: <>
|
|
47
|
+
{country.name}
|
|
48
|
+
</>,
|
|
41
49
|
children: (
|
|
42
50
|
<div>
|
|
43
|
-
<Link to={listingLink(country.
|
|
51
|
+
<Link to={listingLink(country.id)} className="country">
|
|
44
52
|
<img className="country-image" src={`${country.cover}`} />
|
|
45
53
|
<h1 className="country-name"> {country.name} </h1>
|
|
46
|
-
<
|
|
54
|
+
<picture className="bottom-logo">
|
|
55
|
+
<img src="/iceholidays-assets/images/destinations_logo.png"></img>
|
|
56
|
+
</picture>
|
|
47
57
|
</Link>
|
|
48
58
|
{
|
|
49
59
|
country.topCities?.length > 0 &&
|
|
50
60
|
<div className="top-cities">
|
|
51
61
|
<h2>Top Cities</h2>
|
|
52
|
-
<
|
|
53
|
-
{
|
|
54
|
-
country.topCities?.map((city, index)=><Postcard key={index} link={listingLink(city.name)} image={city.cover} name={city.name} tourCount={city.tourCount}/>)
|
|
55
|
-
}
|
|
56
|
-
</div>
|
|
62
|
+
<LocationPostcards locations={country.topCities}/>
|
|
57
63
|
</div>
|
|
58
64
|
}
|
|
59
65
|
{
|
|
@@ -63,7 +69,7 @@ export default class Destinations extends React.Component {
|
|
|
63
69
|
<div className="other-destinations">
|
|
64
70
|
{
|
|
65
71
|
country.tags?.map((city, index)=>{
|
|
66
|
-
return <Link key={index} to={listingLink(city.
|
|
72
|
+
return <Link key={index} to={listingLink(city.id)}> {city.name} </Link>
|
|
67
73
|
})
|
|
68
74
|
}
|
|
69
75
|
</div>
|
|
@@ -75,11 +81,11 @@ export default class Destinations extends React.Component {
|
|
|
75
81
|
<div className="highlights">
|
|
76
82
|
{
|
|
77
83
|
country.highlights?.map((city, index)=>{
|
|
78
|
-
return <Link key={index} to={listingLink(city.
|
|
84
|
+
return <Link key={index} to={listingLink(city.id)}>
|
|
79
85
|
<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
86
|
<div className="details">
|
|
81
87
|
<span className="city-name"> {city.name} </span>
|
|
82
|
-
<span className="tour-count"> {pluralize(city.tourCount, "tour")} <Icon path={mdiMenuRight} size={1} /> </span>
|
|
88
|
+
<span className="tour-count"> {pluralize(city.tourCount || 0, "tour")} <Icon path={mdiMenuRight} size={1} /> </span>
|
|
83
89
|
</div>
|
|
84
90
|
</div>
|
|
85
91
|
</Link>
|
|
@@ -96,10 +102,14 @@ export default class Destinations extends React.Component {
|
|
|
96
102
|
<div id="popular-destinations">
|
|
97
103
|
<h1>Explore Popular Destination</h1>
|
|
98
104
|
|
|
105
|
+
<LocationDropdown locations={this.state.countries} selectLocation={changeTab}/>
|
|
106
|
+
|
|
99
107
|
<Tabs
|
|
100
108
|
defaultActiveKey="1"
|
|
109
|
+
activeKey={this.state.activeTab}
|
|
101
110
|
items={items}
|
|
102
|
-
type="card"
|
|
111
|
+
type="card"
|
|
112
|
+
onChange={changeTab}/>
|
|
103
113
|
|
|
104
114
|
</div>
|
|
105
115
|
)
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
|
|
2
|
+
import { List, Card, Space } from "antd";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { data } from "react-router-dom";
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const pricingCategory = {
|
|
8
|
+
"adult": "Adult",
|
|
9
|
+
"child_no_bed": "Child no bed",
|
|
10
|
+
"child_twin": "Child twin",
|
|
11
|
+
"child_with_bed": "Child with bed",
|
|
12
|
+
"infant": "Infant",
|
|
13
|
+
"single_supplement": "Single Supplement",
|
|
14
|
+
"dta_adult": "DTA Adult",
|
|
15
|
+
"dta_child_no_bed": "DTA Child no bed",
|
|
16
|
+
"dta_child_twin": "DTA Child twin",
|
|
17
|
+
"dta_child_with_bed": "DTA Child with bed",
|
|
18
|
+
"dta_infant": "DTA Infant",
|
|
19
|
+
"dta_single_supplement": "DTA Single Supplement",
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const priceTypes = {
|
|
23
|
+
"normal": "normal",
|
|
24
|
+
"normal1": "normal 1",
|
|
25
|
+
"normal2": "normal 2",
|
|
26
|
+
"normal3": "normal 3",
|
|
27
|
+
"normal4": "normal 4",
|
|
28
|
+
"normal5": "normal 5",
|
|
29
|
+
"normal6": "normal 6",
|
|
30
|
+
"normal7": "normal 7",
|
|
31
|
+
"normal8": "normal 8",
|
|
32
|
+
"normal9": "normal 9",
|
|
33
|
+
"early_bird": "early bird",
|
|
34
|
+
"specialoffer": "special offer",
|
|
35
|
+
"specialdeal": "special deal",
|
|
36
|
+
"superpromo": "super promo",
|
|
37
|
+
"promo": "promo",
|
|
38
|
+
"promo1": "promo 1",
|
|
39
|
+
"promo2": "promo 2",
|
|
40
|
+
"promo3": "promo 3",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
const PriceDetails = (
|
|
45
|
+
props: {
|
|
46
|
+
priceCurrency:string,
|
|
47
|
+
prices: any[]
|
|
48
|
+
}
|
|
49
|
+
) => {
|
|
50
|
+
const { priceCurrency, prices } = props;
|
|
51
|
+
|
|
52
|
+
var pricingTypes:any[] = [], priceTable:any = {}, priceList:any[] = [];
|
|
53
|
+
if(prices.length>0){
|
|
54
|
+
pricingTypes = prices.map((price:any) => price.type);
|
|
55
|
+
prices.forEach((price:any) => {
|
|
56
|
+
const pricingObj = {
|
|
57
|
+
label: price.display_price,
|
|
58
|
+
adult: price.adult,
|
|
59
|
+
child_no_bed: price.child_no_bed,
|
|
60
|
+
child_twin: price.child_twin,
|
|
61
|
+
child_with_bed: price.child_with_bed,
|
|
62
|
+
dta_adult: price.dta_adult,
|
|
63
|
+
dta_child_no_bed: price.dta_child_no_bed,
|
|
64
|
+
dta_child_twin: price.dta_child_twin,
|
|
65
|
+
dta_child_with_bed: price.dta_child_with_bed,
|
|
66
|
+
dta_infant: price.dta_infant,
|
|
67
|
+
dta_single_supplement: price.dta_single_supplement,
|
|
68
|
+
infant: price.infant,
|
|
69
|
+
single_supplement: price.single_supplement,
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
priceTable[price.type] = pricingObj;
|
|
73
|
+
|
|
74
|
+
priceList.push({
|
|
75
|
+
title: `${price.display_price} ${priceTypes[price.type]}`,
|
|
76
|
+
type: price.type,
|
|
77
|
+
...pricingObj
|
|
78
|
+
})
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<div id="price-details" className="details-container">
|
|
84
|
+
<div className="details-container_header"> Price Details </div>
|
|
85
|
+
<div className="details-container_content">
|
|
86
|
+
<table id="pricing-table">
|
|
87
|
+
<thead>
|
|
88
|
+
<tr>
|
|
89
|
+
<th></th>
|
|
90
|
+
{
|
|
91
|
+
pricingTypes.map((type:any) => <th className={type}>
|
|
92
|
+
<div>{priceTable[type].label}</div>
|
|
93
|
+
<div className="price-type">{priceTypes[type]}</div>
|
|
94
|
+
</th>)
|
|
95
|
+
}
|
|
96
|
+
</tr>
|
|
97
|
+
</thead>
|
|
98
|
+
<tbody>
|
|
99
|
+
{
|
|
100
|
+
Object.entries(pricingCategory).map(category => (
|
|
101
|
+
<tr>
|
|
102
|
+
<td> {category[1]} </td>
|
|
103
|
+
{
|
|
104
|
+
pricingTypes.map((type:any) => <td className={type}> {priceCurrency} {priceTable[type][category[0]]}</td>)
|
|
105
|
+
}
|
|
106
|
+
</tr>
|
|
107
|
+
))
|
|
108
|
+
}
|
|
109
|
+
</tbody>
|
|
110
|
+
</table>
|
|
111
|
+
|
|
112
|
+
{
|
|
113
|
+
priceList.length > 0 && (
|
|
114
|
+
<List
|
|
115
|
+
id="pricing-list"
|
|
116
|
+
itemLayout="vertical"
|
|
117
|
+
dataSource={priceList}
|
|
118
|
+
renderItem={(item) => (
|
|
119
|
+
<List.Item>
|
|
120
|
+
<Card className={item.type}>
|
|
121
|
+
<div className="pricing-header"> {item.title} </div>
|
|
122
|
+
<div className="pricing-body">
|
|
123
|
+
<Space size={15} direction="vertical" style={{display: 'flex'}}>
|
|
124
|
+
{
|
|
125
|
+
Object.entries(pricingCategory).map(category => (
|
|
126
|
+
<div className="pricing">
|
|
127
|
+
<div className="category"> {category[1]} </div>
|
|
128
|
+
<div className="price">{priceCurrency} {item[category[0]]} </div>
|
|
129
|
+
</div>
|
|
130
|
+
))
|
|
131
|
+
}
|
|
132
|
+
</Space>
|
|
133
|
+
</div>
|
|
134
|
+
</Card>
|
|
135
|
+
</List.Item>
|
|
136
|
+
)}
|
|
137
|
+
/>
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
</div>
|
|
142
|
+
</div>
|
|
143
|
+
)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export default PriceDetails;
|