@tfehotels/tfe-gatsby-library 1.0.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.
- package/README.md +41 -0
- package/lib/index.browser.cjs +2 -0
- package/lib/index.browser.cjs.map +1 -0
- package/lib/index.browser.d.ts +640 -0
- package/lib/index.browser.esm.js +2 -0
- package/lib/index.browser.esm.js.map +1 -0
- package/lib/index.node.cjs +2 -0
- package/lib/index.node.cjs.map +1 -0
- package/lib/index.node.d.ts +116 -0
- package/lib/index.node.esm.js +2 -0
- package/lib/index.node.esm.js.map +1 -0
- package/package.json +84 -0
- package/src/browser/api/button/index.tsx +16 -0
- package/src/browser/api/button/types.ts +7 -0
- package/src/browser/api/checkbox/index.tsx +74 -0
- package/src/browser/api/checkbox/types.tsx +21 -0
- package/src/browser/api/country-prefix/index.tsx +150 -0
- package/src/browser/api/country-prefix/types.tsx +17 -0
- package/src/browser/api/form/index.tsx +330 -0
- package/src/browser/api/form/types.ts +31 -0
- package/src/browser/api/google-places/index.tsx +90 -0
- package/src/browser/api/google-places/types.ts +24 -0
- package/src/browser/api/icon/index.tsx +26 -0
- package/src/browser/api/icon/types.tsx +24 -0
- package/src/browser/api/image/index.tsx +91 -0
- package/src/browser/api/image/types.ts +11 -0
- package/src/browser/api/input/index.tsx +44 -0
- package/src/browser/api/input/types.ts +10 -0
- package/src/browser/api/link/index.tsx +54 -0
- package/src/browser/api/link/types.ts +24 -0
- package/src/browser/api/linklist/index.tsx +17 -0
- package/src/browser/api/linklist/types.ts +6 -0
- package/src/browser/api/select/index.tsx +99 -0
- package/src/browser/api/select/types.ts +24 -0
- package/src/browser/api/svg/index.tsx +8 -0
- package/src/browser/api/svg/types.ts +8 -0
- package/src/browser/api/text/index.tsx +14 -0
- package/src/browser/api/text/types.ts +12 -0
- package/src/browser/api/textarea/index.tsx +12 -0
- package/src/browser/api/title/index.tsx +19 -0
- package/src/browser/api/title/types.ts +10 -0
- package/src/browser/api/types.ts +245 -0
- package/src/browser/carousel/buttons/index.tsx +81 -0
- package/src/browser/carousel/buttons/types.ts +15 -0
- package/src/browser/carousel/dots/index.tsx +53 -0
- package/src/browser/carousel/dots/types.ts +14 -0
- package/src/browser/carousel/index.tsx +131 -0
- package/src/browser/carousel/types.ts +21 -0
- package/src/browser/markdown/index.tsx +41 -0
- package/src/browser/markdown/types.ts +11 -0
- package/src/browser/modal/index.tsx +35 -0
- package/src/browser/modal/types.ts +9 -0
- package/src/browser/spinner/index.tsx +19 -0
- package/src/browser/spinner/types.ts +5 -0
- package/src/browser/toast/index.tsx +84 -0
- package/src/browser/use_viewport/index.tsx +34 -0
- package/src/browser/use_viewport/types.ts +5 -0
- package/src/browser/utils/animation.ts +12 -0
- package/src/browser/utils/booking_engine.ts +180 -0
- package/src/browser/utils/eclub.ts +30 -0
- package/src/browser/utils/forms.ts +76 -0
- package/src/browser/utils/hotel.ts +25 -0
- package/src/browser/utils/image.ts +63 -0
- package/src/browser/utils/location.ts +213 -0
- package/src/browser/utils/notifications.tsx +25 -0
- package/src/browser/utils/number.ts +6 -0
- package/src/browser/utils/requests.ts +2 -0
- package/src/browser/utils/search.ts +25 -0
- package/src/browser/utils/string.ts +9 -0
- package/src/browser/utils/types.ts +106 -0
- package/src/browser/utils/url.ts +116 -0
- package/src/browser/utils/viewport.ts +59 -0
- package/src/index.browser.ts +103 -0
- package/src/index.node.ts +16 -0
- package/src/node/api/index.ts +174 -0
- package/src/node/api/types.ts +19 -0
- package/src/node/build/index.ts +142 -0
- package/src/node/build/types.ts +5 -0
- package/src/node/config/index.ts +149 -0
- package/src/node/form/index.ts +23 -0
- package/src/node/form/types.ts +3 -0
- package/src/node/property/index.ts +78 -0
- package/src/node/property/types.ts +25 -0
- package/src/node/url/index.ts +8 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import React, { FC, ReactNode } from "react"
|
|
2
|
+
import { toast, ToastPosition } from "react-hot-toast"
|
|
3
|
+
import { keyframes } from "goober"
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const enterAnimation = (factor: number) => `
|
|
7
|
+
0% {transform: translate3d(0,${factor * -200}%,0) scale(.6); opacity:.5;}
|
|
8
|
+
100% {transform: translate3d(0,0,0) scale(1); opacity:1;}
|
|
9
|
+
`
|
|
10
|
+
|
|
11
|
+
const exitAnimation = (factor: number) => `
|
|
12
|
+
0% {transform: translate3d(0,0,-1px) scale(1); opacity:1;}
|
|
13
|
+
100% {transform: translate3d(0,${factor * -150}%,-1px) scale(.6); opacity:0;}
|
|
14
|
+
`
|
|
15
|
+
|
|
16
|
+
const fadeInAnimation = `0%{opacity:0;} 100%{opacity:1;}`
|
|
17
|
+
const fadeOutAnimation = `0%{opacity:1;} 100%{opacity:0;}`
|
|
18
|
+
|
|
19
|
+
const prefersReducedMotion = (() => {
|
|
20
|
+
// Cache result
|
|
21
|
+
let shouldReduceMotion: boolean | undefined = undefined
|
|
22
|
+
|
|
23
|
+
return () => {
|
|
24
|
+
if (shouldReduceMotion === undefined && typeof window !== "undefined") {
|
|
25
|
+
const mediaQuery = matchMedia("(prefers-reduced-motion: reduce)")
|
|
26
|
+
shouldReduceMotion = !mediaQuery || mediaQuery.matches
|
|
27
|
+
}
|
|
28
|
+
return shouldReduceMotion
|
|
29
|
+
}
|
|
30
|
+
})()
|
|
31
|
+
|
|
32
|
+
const getAnimationStyle = (
|
|
33
|
+
position: ToastPosition,
|
|
34
|
+
visible: boolean
|
|
35
|
+
): React.CSSProperties => {
|
|
36
|
+
const top = position.includes("top")
|
|
37
|
+
const factor = top ? 1 : -1
|
|
38
|
+
|
|
39
|
+
const [enter, exit] = prefersReducedMotion()
|
|
40
|
+
? [fadeInAnimation, fadeOutAnimation]
|
|
41
|
+
: [enterAnimation(factor), exitAnimation(factor)]
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
animation: visible
|
|
45
|
+
? `${keyframes(enter)} 0.35s cubic-bezier(.21,1.02,.73,1) forwards`
|
|
46
|
+
: `${keyframes(exit)} 0.4s forwards cubic-bezier(.06,.71,.55,1)`,
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ToastProps {
|
|
51
|
+
id: string
|
|
52
|
+
visible: boolean
|
|
53
|
+
message: string | ReactNode
|
|
54
|
+
height?: number
|
|
55
|
+
position?: ToastPosition
|
|
56
|
+
variant: "error" | "info" | "success"
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const Toast: FC<ToastProps> = ({
|
|
60
|
+
id,
|
|
61
|
+
visible,
|
|
62
|
+
message,
|
|
63
|
+
height,
|
|
64
|
+
position,
|
|
65
|
+
variant,
|
|
66
|
+
}) => {
|
|
67
|
+
const animationStyle: React.CSSProperties = height
|
|
68
|
+
? getAnimationStyle(position || "top-center", visible)
|
|
69
|
+
: { opacity: 0 }
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<div
|
|
73
|
+
className={`toast ${visible ? "animate-enter" : "animate-leave"} ${variant}`}
|
|
74
|
+
style={{
|
|
75
|
+
...animationStyle,
|
|
76
|
+
}}
|
|
77
|
+
>
|
|
78
|
+
<button className={"close"} onClick={() => toast.dismiss(id)}>X</button>
|
|
79
|
+
{message}
|
|
80
|
+
</div>
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export default Toast
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { useState, useEffect, startTransition } from "react"
|
|
2
|
+
import { ViewportType } from "./types"
|
|
3
|
+
|
|
4
|
+
function UseViewport() {
|
|
5
|
+
const [viewport, setViewport] = useState<ViewportType>( {
|
|
6
|
+
isMobile: false,
|
|
7
|
+
isTablet: false,
|
|
8
|
+
isDesktop: true,
|
|
9
|
+
}
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
const handleResize = () => {
|
|
14
|
+
startTransition(() => setViewport(
|
|
15
|
+
{
|
|
16
|
+
isMobile: window.innerWidth < 769,
|
|
17
|
+
isTablet: window.innerWidth >= 769 && window.innerWidth < 1024,
|
|
18
|
+
isDesktop: window.innerWidth >= 1024,
|
|
19
|
+
}
|
|
20
|
+
))
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
handleResize()
|
|
24
|
+
|
|
25
|
+
window.addEventListener("resize", handleResize)
|
|
26
|
+
return () => {
|
|
27
|
+
window.removeEventListener("resize", handleResize)
|
|
28
|
+
}
|
|
29
|
+
}, [])
|
|
30
|
+
|
|
31
|
+
return viewport
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export default UseViewport
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { Stage } from "transition-hook";
|
|
2
|
+
|
|
3
|
+
export const animateJSX = (
|
|
4
|
+
transition: {
|
|
5
|
+
stage: Stage;
|
|
6
|
+
shouldMount: boolean;
|
|
7
|
+
},
|
|
8
|
+
animation: string = "presence"
|
|
9
|
+
): string =>
|
|
10
|
+
`jsx__${animation} jsx__${animation}__${
|
|
11
|
+
["from", "leave"].includes(transition.stage) ? "loading" : "loaded"
|
|
12
|
+
}`;
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import format from "date-fns/format";
|
|
2
|
+
import { IsMobile } from "./viewport";
|
|
3
|
+
import {
|
|
4
|
+
BookingURLsType,
|
|
5
|
+
HotelOptionsByCityType,
|
|
6
|
+
HotelPagesType,
|
|
7
|
+
SearchParamsType,
|
|
8
|
+
} from "./types";
|
|
9
|
+
|
|
10
|
+
function RenderTemplate(str: string, obj: any) {
|
|
11
|
+
let parts = str.split(/\$\{(?!\d)[\wæøåÆØÅ]*\}/);
|
|
12
|
+
let args = str.match(/[^{}]+(?=})/g) || [];
|
|
13
|
+
let parameters = args.map(
|
|
14
|
+
(argument) =>
|
|
15
|
+
obj[argument] || (obj[argument] === undefined ? "" : obj[argument])
|
|
16
|
+
);
|
|
17
|
+
return String.raw({ raw: parts }, ...parameters);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export const BookingUrl = (
|
|
21
|
+
hotel_code: string,
|
|
22
|
+
urls: BookingURLsType["booking_urls"],
|
|
23
|
+
searchParams: SearchParamsType,
|
|
24
|
+
hasAvailability: boolean,
|
|
25
|
+
rate_code: string | null = null,
|
|
26
|
+
room_code: string | null = null
|
|
27
|
+
) => {
|
|
28
|
+
let params = RenderTemplate(urls.variables, {
|
|
29
|
+
hotel_code: hotel_code,
|
|
30
|
+
check_in: format(
|
|
31
|
+
searchParams.check_in,
|
|
32
|
+
urls.date_format.replace(/m/g, "M")
|
|
33
|
+
),
|
|
34
|
+
check_out: format(
|
|
35
|
+
searchParams.check_out,
|
|
36
|
+
urls.date_format.replace(/m/g, "M")
|
|
37
|
+
),
|
|
38
|
+
adult_count: searchParams.adult_count,
|
|
39
|
+
child_count: searchParams.child_count,
|
|
40
|
+
total_rooms: 1,
|
|
41
|
+
rate_code: !searchParams.promo_codes.length && rate_code ? rate_code : "",
|
|
42
|
+
promo_code: searchParams.promo_codes.length
|
|
43
|
+
? searchParams.promo_codes[0]
|
|
44
|
+
: "",
|
|
45
|
+
});
|
|
46
|
+
if (room_code) {
|
|
47
|
+
params += "&room=" + room_code;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
let url = IsMobile()
|
|
51
|
+
? (hasAvailability ? urls.mobile_url : urls.mobile_url_no_availability) +
|
|
52
|
+
"&" +
|
|
53
|
+
params
|
|
54
|
+
: (hasAvailability ? urls.url : urls.url_no_availability) + "&" + params;
|
|
55
|
+
|
|
56
|
+
// console.log(searchParams)
|
|
57
|
+
// console.log(url)
|
|
58
|
+
window.open(url, "_blank");
|
|
59
|
+
// window.location.href = url
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const ParseAvailability = (data: any) => {
|
|
63
|
+
if (!data.success) {
|
|
64
|
+
return {};
|
|
65
|
+
}
|
|
66
|
+
data.data.forEach((hotel: any) => {
|
|
67
|
+
hotel.room_rates = {};
|
|
68
|
+
hotel.rates.forEach((room: any) =>
|
|
69
|
+
room.forEach((room_rate: any) => {
|
|
70
|
+
if (!hotel.room_rates.hasOwnProperty(room_rate.room_type_code)) {
|
|
71
|
+
hotel.room_rates[room_rate.room_type_code] = [];
|
|
72
|
+
}
|
|
73
|
+
hotel.room_rates[room_rate.room_type_code].push(room_rate);
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
return data.data;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
*
|
|
82
|
+
* Returns Select options sorting by the cities with more hotels, otherwise alphabetically.
|
|
83
|
+
*
|
|
84
|
+
* For instance:
|
|
85
|
+
*
|
|
86
|
+
* [
|
|
87
|
+
* {
|
|
88
|
+
* label: 'Sydney',
|
|
89
|
+
* options: [
|
|
90
|
+
* { label: 'Vibe Hotel Sydney', value: "58443" },
|
|
91
|
+
* { label: 'Vibe Hotel Sydney Darling Harbour', value: "8565" }
|
|
92
|
+
* ]
|
|
93
|
+
* },
|
|
94
|
+
* {
|
|
95
|
+
* label: 'Mebourne',
|
|
96
|
+
* options: [
|
|
97
|
+
* { label: 'Vibe Hotel Melbourne', value: "9969" },
|
|
98
|
+
* ]
|
|
99
|
+
* },
|
|
100
|
+
* ]
|
|
101
|
+
*/
|
|
102
|
+
export const hotelOptionsByCity = (
|
|
103
|
+
hotelPages: HotelPagesType
|
|
104
|
+
): HotelOptionsByCityType[] => {
|
|
105
|
+
let hotelsByCity: { [k: string]: { label: string; value: string }[] } = {
|
|
106
|
+
Sydney: [],
|
|
107
|
+
Melbourne: [],
|
|
108
|
+
Perth: [],
|
|
109
|
+
Hobart: [],
|
|
110
|
+
Canberra: [],
|
|
111
|
+
Adelaide: [],
|
|
112
|
+
"Gold Coast": [],
|
|
113
|
+
Darwin: [],
|
|
114
|
+
Singapore: [],
|
|
115
|
+
};
|
|
116
|
+
const propertyOrder = Object.entries(hotelPages).reduce(
|
|
117
|
+
(acc, [id, hotel]) => {
|
|
118
|
+
if (hotelsByCity[hotel.city]) {
|
|
119
|
+
hotelsByCity[hotel.city].push({ label: hotel.name, value: id });
|
|
120
|
+
acc[id] = hotel.sort;
|
|
121
|
+
}
|
|
122
|
+
return acc;
|
|
123
|
+
},
|
|
124
|
+
{} as { [k: string]: number }
|
|
125
|
+
);
|
|
126
|
+
return Object.entries(hotelsByCity).map(([city, options]) => {
|
|
127
|
+
return {
|
|
128
|
+
label: city,
|
|
129
|
+
options: options.sort(
|
|
130
|
+
(a, b) => propertyOrder[a.value] - propertyOrder[b.value]
|
|
131
|
+
),
|
|
132
|
+
};
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
export const SearchAvailability = async (
|
|
137
|
+
check_in: Date,
|
|
138
|
+
check_out: Date,
|
|
139
|
+
adult_count: number,
|
|
140
|
+
child_count: number,
|
|
141
|
+
promo_codes: string[],
|
|
142
|
+
rate_codes: string[],
|
|
143
|
+
hotel_codes: string[]
|
|
144
|
+
) => {
|
|
145
|
+
if (!hotel_codes) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
'"hotel_codes" must be defined within the page attributes!'
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
return fetch(
|
|
151
|
+
`${process.env.SABRE_PROTOCOL}://${process.env.SABRE_HOST}/availability`,
|
|
152
|
+
{
|
|
153
|
+
headers: { "Content-Type": "application/json" },
|
|
154
|
+
method: "POST",
|
|
155
|
+
body: JSON.stringify({
|
|
156
|
+
check_in: format(check_in, "yyyy-MM-dd"),
|
|
157
|
+
check_out: format(check_out, "yyyy-MM-dd"),
|
|
158
|
+
hotel_codes: hotel_codes,
|
|
159
|
+
adult_count: adult_count,
|
|
160
|
+
child_count: child_count,
|
|
161
|
+
area_codes: [],
|
|
162
|
+
promo_codes: promo_codes,
|
|
163
|
+
rate_codes: rate_codes,
|
|
164
|
+
price_range: { min: 0, max: 0 },
|
|
165
|
+
is_mobile: IsMobile(),
|
|
166
|
+
language: "en",
|
|
167
|
+
}),
|
|
168
|
+
}
|
|
169
|
+
)
|
|
170
|
+
.then((response) => response.json())
|
|
171
|
+
.then(
|
|
172
|
+
(data) => ParseAvailability(data),
|
|
173
|
+
(error) => {
|
|
174
|
+
console.log(error);
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
export const pricePerNight = (total: string, nights: number): string =>
|
|
180
|
+
(parseFloat(total) / nights).toFixed(2);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { UserType } from "../api/types";
|
|
2
|
+
|
|
3
|
+
export const saveUserSession = (data: any) => {
|
|
4
|
+
sessionStorage.setItem(
|
|
5
|
+
"user",
|
|
6
|
+
JSON.stringify({
|
|
7
|
+
loyaltyprofileId: data.loyaltyprofileId,
|
|
8
|
+
profile_id: data.profile_id,
|
|
9
|
+
title: data.title,
|
|
10
|
+
first_name: data.first_name,
|
|
11
|
+
last_name: data.last_name,
|
|
12
|
+
email: data.email,
|
|
13
|
+
zip: data.zip,
|
|
14
|
+
country_code: data.country_code,
|
|
15
|
+
MobilePhoneCountryCode: data.MobilePhoneCountryCode,
|
|
16
|
+
mobile: data.mobile,
|
|
17
|
+
date_of_birth: data.date_of_birth,
|
|
18
|
+
language: data.language,
|
|
19
|
+
} as UserType)
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export const loadUserSession = (): UserType | null => {
|
|
24
|
+
const user = sessionStorage.getItem("user");
|
|
25
|
+
return user ? JSON.parse(user) : null;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const deleteUserSession = () => {
|
|
29
|
+
sessionStorage.removeItem("user");
|
|
30
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
export interface RequestParamsType {
|
|
2
|
+
[param: string]: string | number | null;
|
|
3
|
+
}
|
|
4
|
+
export interface HeadersType {
|
|
5
|
+
[param: string]: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface APIResponseType {
|
|
9
|
+
success?: boolean;
|
|
10
|
+
errors?: { [input: string]: string[] };
|
|
11
|
+
error?: string;
|
|
12
|
+
msg?: string;
|
|
13
|
+
status: number;
|
|
14
|
+
[k: string]: any;
|
|
15
|
+
}
|
|
16
|
+
export const json_request = async (
|
|
17
|
+
url: string,
|
|
18
|
+
params: RequestParamsType,
|
|
19
|
+
method: "get" | "post" = "post",
|
|
20
|
+
headers: HeadersType = {}
|
|
21
|
+
): Promise<APIResponseType> => {
|
|
22
|
+
if (url.includes(process.env.API_HOST as string)) {
|
|
23
|
+
headers["project-id"] = process.env.PROJECT as string;
|
|
24
|
+
}
|
|
25
|
+
let config: any = {
|
|
26
|
+
headers: {
|
|
27
|
+
"Content-Type": "application/json",
|
|
28
|
+
...headers,
|
|
29
|
+
},
|
|
30
|
+
method: method.toUpperCase(),
|
|
31
|
+
};
|
|
32
|
+
if (method === "post") {
|
|
33
|
+
config.body = JSON.stringify(params);
|
|
34
|
+
} else {
|
|
35
|
+
url = `${url}?${new URLSearchParams(
|
|
36
|
+
Object.entries(params).map(([k, v]) => [k, v === null ? "" : `${v}`])
|
|
37
|
+
)}`;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
let response = await fetch(url, config);
|
|
41
|
+
return { ...(await response.json()), status: response.status };
|
|
42
|
+
} catch (error) {
|
|
43
|
+
console.error(error);
|
|
44
|
+
return { success: false, error: "Request failed", status: 400 };
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const INPUT_EVENT_VALUES: any = {
|
|
49
|
+
select: (e: any) => e.value,
|
|
50
|
+
input: (e: any) => e.target.value,
|
|
51
|
+
textarea: (e: any) => e.target.value,
|
|
52
|
+
"google-places": (e: any) => e.target.value,
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const getInputEventValue = (inputType: string, event: any) =>
|
|
56
|
+
INPUT_EVENT_VALUES.hasOwnProperty(inputType)
|
|
57
|
+
? INPUT_EVENT_VALUES[inputType](event)
|
|
58
|
+
: event;
|
|
59
|
+
|
|
60
|
+
const INPUT_HUMAN_VALUES: any = {};
|
|
61
|
+
|
|
62
|
+
export const getInputHumanValue = (inputType: string, value: any) =>
|
|
63
|
+
INPUT_HUMAN_VALUES.hasOwnProperty(inputType)
|
|
64
|
+
? INPUT_HUMAN_VALUES[inputType](value)
|
|
65
|
+
: value;
|
|
66
|
+
|
|
67
|
+
export const INPUT_FIELDS = new Set([
|
|
68
|
+
"input",
|
|
69
|
+
"select",
|
|
70
|
+
"textarea",
|
|
71
|
+
"checkbox",
|
|
72
|
+
"button",
|
|
73
|
+
"recaptcha2",
|
|
74
|
+
"google-places",
|
|
75
|
+
"country-prefix",
|
|
76
|
+
]);
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export const joinAddress = (addressParams: (string | null)[]) =>
|
|
2
|
+
addressParams
|
|
3
|
+
.reduce((acc: string[], param: string | null) => {
|
|
4
|
+
if (param) {
|
|
5
|
+
acc.push(param)
|
|
6
|
+
}
|
|
7
|
+
return acc
|
|
8
|
+
}, [])
|
|
9
|
+
.join(", ")
|
|
10
|
+
|
|
11
|
+
export interface PageTagsType {
|
|
12
|
+
[k: string]: string
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const tagExists = (
|
|
16
|
+
pageTags: PageTagsType,
|
|
17
|
+
...tags: string[]
|
|
18
|
+
): boolean => {
|
|
19
|
+
for (const tag of Object.keys(pageTags)) {
|
|
20
|
+
if (tags.includes(tag)) {
|
|
21
|
+
return true
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return false
|
|
25
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { ImageMediaType, ImageType } from "../api/types";
|
|
2
|
+
import { IsMobile } from "./viewport";
|
|
3
|
+
|
|
4
|
+
export const preloadImages = async (images: string[]) => {
|
|
5
|
+
images.forEach((url: string) => {
|
|
6
|
+
if (url) {
|
|
7
|
+
const img = new Image();
|
|
8
|
+
img.src = url;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export const optimizedThumb = (img: ImageType): string =>
|
|
14
|
+
img.optimized ? `${img.url}${img.name}-20w.webp` : img.url;
|
|
15
|
+
|
|
16
|
+
// Extract the Thumb image from the provided Image (if any)
|
|
17
|
+
export const getThumb = (img: {
|
|
18
|
+
image?: ImageType;
|
|
19
|
+
mobile_image?: ImageType;
|
|
20
|
+
}): string => {
|
|
21
|
+
if (img.image || img.mobile_image) {
|
|
22
|
+
return IsMobile()
|
|
23
|
+
? img.mobile_image
|
|
24
|
+
? optimizedThumb(img.mobile_image) // @ts-ignore
|
|
25
|
+
: optimizedThumb(img.image)
|
|
26
|
+
: img.image
|
|
27
|
+
? optimizedThumb(img.image) // @ts-ignore
|
|
28
|
+
: optimizedThumb(img.mobile_image);
|
|
29
|
+
}
|
|
30
|
+
return "";
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Creates a valid image element
|
|
34
|
+
export const toImageElement = (obj: any, keyMap: any = {}): ImageMediaType => {
|
|
35
|
+
return Object.entries(obj).reduce(
|
|
36
|
+
(acc: any, [key, value]: any) => {
|
|
37
|
+
acc[keyMap[key] || key] = value;
|
|
38
|
+
return acc;
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: "image",
|
|
42
|
+
title: "",
|
|
43
|
+
loading: "eager",
|
|
44
|
+
image: undefined,
|
|
45
|
+
mobile_image: undefined,
|
|
46
|
+
mobile_file: undefined,
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// Slick breaks when his number or slides is equal or less than the param "slidesToShow"
|
|
52
|
+
// Therefore we duplicate the slides in case of not having enough of them:
|
|
53
|
+
export const minimumSlides = (slidesToShow: number, slides: any[]) =>
|
|
54
|
+
slides.length >= slidesToShow
|
|
55
|
+
? slides
|
|
56
|
+
: [].concat(
|
|
57
|
+
...new Array(Math.ceil(slidesToShow / slides.length)).fill(slides)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
export const tripadvisorRatingURL = (score: string) =>
|
|
61
|
+
`https://www.tripadvisor.com/img/cdsi/img2/ratings/traveler/${parseFloat(
|
|
62
|
+
score
|
|
63
|
+
).toFixed(1)}-41022-5.svg`;
|