ublo-lib 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/es/common/components/admin-links/admin-links.js +107 -0
- package/es/common/components/admin-links/admin-links.module.css +85 -0
- package/es/common/components/admin-links/index.js +2 -0
- package/es/common/components/analytics.js +46 -0
- package/es/common/components/breadcrumb.js +69 -0
- package/es/common/components/carousel-zone.js +61 -0
- package/es/common/components/carousel.js +365 -0
- package/es/common/components/cookie-consent/cookie-consent.js +111 -0
- package/es/common/components/cookie-consent/cookie-consent.module.css +103 -0
- package/es/common/components/cookie-consent/index.js +3 -0
- package/es/common/components/cookie-consent/messages.js +31 -0
- package/es/common/components/custom-contact-form/attachment.js +229 -0
- package/es/common/components/custom-contact-form/attachment.module.css +211 -0
- package/es/common/components/custom-contact-form/custom-contact-form.js +168 -0
- package/es/common/components/custom-contact-form/field.js +294 -0
- package/es/common/components/custom-contact-form/field.module.css +17 -0
- package/es/common/components/custom-contact-form/icons.js +55 -0
- package/es/common/components/custom-contact-form/index.js +2 -0
- package/es/common/components/custom-contact-form/index.module.css +119 -0
- package/es/common/components/custom-contact-form/messages.js +79 -0
- package/es/common/components/custom-contact-form/utils.js +132 -0
- package/es/common/components/date-picker/calendar.js +246 -0
- package/es/common/components/date-picker/calendar.module.css +123 -0
- package/es/common/components/date-picker/data.js +96 -0
- package/es/common/components/date-picker/date-item.js +127 -0
- package/es/common/components/date-picker/date-item.module.css +78 -0
- package/es/common/components/date-picker/date-picker.js +119 -0
- package/es/common/components/date-picker/date-picker.module.css +111 -0
- package/es/common/components/date-picker/helper.js +41 -0
- package/es/common/components/date-picker/helper.module.css +81 -0
- package/es/common/components/date-picker/index.js +3 -0
- package/es/common/components/date-picker/messages.js +34 -0
- package/es/common/components/date-picker/modes.js +42 -0
- package/es/common/components/date-picker/modes.module.css +90 -0
- package/es/common/components/date-picker/month.js +78 -0
- package/es/common/components/date-picker/month.module.css +54 -0
- package/es/common/components/date-picker/utils.js +121 -0
- package/es/common/components/error-404/error-404.js +51 -0
- package/es/common/components/error-404/error-404.module.css +55 -0
- package/es/common/components/error-404/index.js +2 -0
- package/es/common/components/error-404/messages.js +29 -0
- package/es/common/components/faq.js +54 -0
- package/es/common/components/info-buttons.js +116 -0
- package/es/common/components/plausible/hooks/use-plausible.js +33 -0
- package/es/common/components/plausible/index.js +7 -0
- package/es/common/components/plausible/plausible.js +20 -0
- package/es/common/components/plausible/services/callback.js +63 -0
- package/es/common/components/plausible/services/load.js +7 -0
- package/es/common/components/plausible/services/send-goal.js +10 -0
- package/es/common/components/popup.js +90 -0
- package/es/common/components/scroll-spy.js +53 -0
- package/es/common/components/tabbed-zones.js +110 -0
- package/es/common/components/unsupported-browser.js +158 -0
- package/es/common/components/video-player/controls.js +125 -0
- package/es/common/components/video-player/icons.js +45 -0
- package/es/common/components/video-player/index.js +1 -0
- package/es/common/components/video-player/player.module.css +151 -0
- package/es/common/components/video-player/utils.js +43 -0
- package/es/common/components/video-player/video-player.js +111 -0
- package/es/common/hooks/use-faq.js +44 -0
- package/es/common/hooks/use-in-view.js +73 -0
- package/es/common/hooks/use-injected-cms-markup.js +50 -0
- package/es/common/hooks/use-packages.js +127 -0
- package/es/common/hooks/use-scroll-direction.js +44 -0
- package/es/common/hooks/use-stay.js +32 -0
- package/es/common/hooks/use-sticky.js +38 -0
- package/es/common/hooks/use-tunnel.js +50 -0
- package/es/common/hooks/use-window-sizes.js +37 -0
- package/es/common/hooks/use-youtube-popup.js +62 -0
- package/es/common/hooks/use-zone-sync.js +65 -0
- package/es/common/utils/cookies.js +13 -0
- package/es/common/utils/copy.js +11 -0
- package/es/common/utils/dates.js +8 -0
- package/es/common/utils/events.js +25 -0
- package/es/common/utils/fetcher.js +37 -0
- package/es/common/utils/file-manager.js +14 -0
- package/es/common/utils/load-js.js +11 -0
- package/es/common/utils/msem-widget.js +16 -0
- package/es/common/utils/touch-device.js +1 -0
- package/es/common/utils/url-parameters.js +12 -0
- package/es/empty.d.ts +4 -0
- package/es/empty.d.ts.map +1 -0
- package/es/empty.js +6 -0
- package/es/esf/components/booking-form/data.js +213 -0
- package/es/esf/components/booking-form/field.js +140 -0
- package/es/esf/components/booking-form/hooks/use-custom-fields.js +20 -0
- package/es/esf/components/booking-form/hooks/use-stay.js +12 -0
- package/es/esf/components/booking-form/icons.js +50 -0
- package/es/esf/components/booking-form/index.js +78 -0
- package/es/esf/components/booking-form/lesson.js +59 -0
- package/es/esf/components/booking-form/lessons.js +93 -0
- package/es/esf/components/booking-form/messages.js +52 -0
- package/es/esf/components/booking-form/personal-data.js +73 -0
- package/es/esf/components/booking-form/progress-bar.js +35 -0
- package/es/esf/components/booking-form/response.js +42 -0
- package/es/esf/components/booking-form/steps.js +81 -0
- package/es/esf/components/booking-form/summary.js +138 -0
- package/es/esf/components/booking-form/utils.js +72 -0
- package/es/esf/components/contact-form/api.js +36 -0
- package/es/esf/components/contact-form/contact-form.js +293 -0
- package/es/esf/components/contact-form/contact-form.module.css +51 -0
- package/es/esf/components/contact-form/data.js +53 -0
- package/es/esf/components/contact-form/index.js +2 -0
- package/es/esf/components/contact-form/messages.js +75 -0
- package/es/esf/components/contact-form/validation.js +63 -0
- package/es/esf/components/covid-link/index.js +119 -0
- package/es/esf/components/covid-link/index.module.css +108 -0
- package/es/esf/components/covid-link/mask-icon.js +17 -0
- package/es/esf/components/covid-link/vax-pass-icon.js +34 -0
- package/es/esf/components/cp-form.js +65 -0
- package/es/esf/components/instructor-suggestions/fetcher.js +17 -0
- package/es/esf/components/instructor-suggestions/icons.js +266 -0
- package/es/esf/components/instructor-suggestions/index.js +181 -0
- package/es/esf/components/instructor-suggestions/loader.js +10 -0
- package/es/esf/components/instructor-suggestions/messages.js +16 -0
- package/es/esf/components/instructors-book/container.js +18 -0
- package/es/esf/components/instructors-book/details.js +120 -0
- package/es/esf/components/instructors-book/icons.js +266 -0
- package/es/esf/components/instructors-book/index.js +15 -0
- package/es/esf/components/instructors-book/link.js +17 -0
- package/es/esf/components/instructors-book/list-utils.js +21 -0
- package/es/esf/components/instructors-book/list.js +184 -0
- package/es/esf/components/instructors-book/loader.js +10 -0
- package/es/esf/components/instructors-book/messages.js +44 -0
- package/es/esf/components/instructors-book/utils.js +5 -0
- package/es/esf/components/levels.js +265 -0
- package/es/esf/components/loyal-customers/api.js +24 -0
- package/es/esf/components/loyal-customers/components/bin-icon.js +31 -0
- package/es/esf/components/loyal-customers/components/bin-icon.module.css +9 -0
- package/es/esf/components/loyal-customers/components/customer-form.js +105 -0
- package/es/esf/components/loyal-customers/components/customer-form.module.css +40 -0
- package/es/esf/components/loyal-customers/components/field.js +119 -0
- package/es/esf/components/loyal-customers/components/field.module.css +3 -0
- package/es/esf/components/loyal-customers/components/row.js +77 -0
- package/es/esf/components/loyal-customers/components/row.module.css +95 -0
- package/es/esf/components/loyal-customers/components/rows.js +38 -0
- package/es/esf/components/loyal-customers/components/rows.module.css +11 -0
- package/es/esf/components/loyal-customers/components/stay.js +37 -0
- package/es/esf/components/loyal-customers/components/stay.module.css +18 -0
- package/es/esf/components/loyal-customers/components/student-form.js +105 -0
- package/es/esf/components/loyal-customers/components/student-form.module.css +68 -0
- package/es/esf/components/loyal-customers/components/voucher.js +26 -0
- package/es/esf/components/loyal-customers/components/voucher.module.css +7 -0
- package/es/esf/components/loyal-customers/content.js +55 -0
- package/es/esf/components/loyal-customers/data.js +131 -0
- package/es/esf/components/loyal-customers/hooks/use-stored-rows.js +14 -0
- package/es/esf/components/loyal-customers/index.js +2 -0
- package/es/esf/components/loyal-customers/loyal-customers.js +141 -0
- package/es/esf/components/loyal-customers/loyal-customers.module.css +62 -0
- package/es/esf/components/loyal-customers/messages.js +59 -0
- package/es/esf/components/loyal-customers/utils.js +81 -0
- package/es/esf/components/village-maps/icons.js +35 -0
- package/es/esf/components/village-maps/index.js +214 -0
- package/es/esf/components/village-maps/messages.js +19 -0
- package/es/esf/components/village-maps/utils.js +26 -0
- package/es/esf/components/week-picker/index.js +244 -0
- package/es/esf/components/week-picker/messages.js +36 -0
- package/es/esf/components/week-picker/utils.js +65 -0
- package/es/esf/components/week-picker/week.js +52 -0
- package/es/esf/components/week-picker-2/index.js +283 -0
- package/es/esf/components/week-picker-2/messages.js +27 -0
- package/es/esf/components/week-picker-2/utils.js +65 -0
- package/es/esf/components/week-picker-2/week.js +55 -0
- package/es/esf/hooks/use-affiliation.js +26 -0
- package/es/esf/hooks/use-booking-links.js +36 -0
- package/es/esf/hooks/use-reviews.js +28 -0
- package/es/esf/hooks/use-season-products.js +100 -0
- package/package.json +51 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import classnames from "classnames";
|
|
3
|
+
import ReactPlayer from "react-player/lazy";
|
|
4
|
+
import Controls from "./controls";
|
|
5
|
+
import * as Utils from "./utils";
|
|
6
|
+
import styles from "./player.module.css";
|
|
7
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
8
|
+
import { jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
|
+
|
|
10
|
+
const Player = ({
|
|
11
|
+
url,
|
|
12
|
+
thumb,
|
|
13
|
+
forcePlay,
|
|
14
|
+
hideCustomControls,
|
|
15
|
+
...props
|
|
16
|
+
}) => {
|
|
17
|
+
const containerRef = React.useRef();
|
|
18
|
+
const playRef = React.useRef();
|
|
19
|
+
const [mounted, setMounted] = React.useState(false);
|
|
20
|
+
const [playing, setPlaying] = React.useState(false);
|
|
21
|
+
const [started, setStarted] = React.useState(false);
|
|
22
|
+
const [progress, setProgress] = React.useState(0);
|
|
23
|
+
const [loadedAmount, setLoadedAmount] = React.useState(0);
|
|
24
|
+
const [newProgress, setNewProgress] = React.useState();
|
|
25
|
+
const [duration, setDuration] = React.useState(0);
|
|
26
|
+
const [currentTime, setCurrentTime] = React.useState(0);
|
|
27
|
+
const [fullScreen, setFullScreen] = React.useState();
|
|
28
|
+
const [muted, setMuted] = React.useState(true);
|
|
29
|
+
|
|
30
|
+
const tick = e => {
|
|
31
|
+
const player = playRef.current;
|
|
32
|
+
const {
|
|
33
|
+
played,
|
|
34
|
+
playedSeconds,
|
|
35
|
+
loaded
|
|
36
|
+
} = e;
|
|
37
|
+
if (!player) return;
|
|
38
|
+
setProgress(played);
|
|
39
|
+
setLoadedAmount(loaded);
|
|
40
|
+
setCurrentTime(playedSeconds);
|
|
41
|
+
setDuration(player.getDuration());
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const updatePlaying = () => {
|
|
45
|
+
setPlaying(!playing);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const startVideo = () => setStarted(true);
|
|
49
|
+
|
|
50
|
+
const endVideo = () => setPlaying(false);
|
|
51
|
+
|
|
52
|
+
React.useEffect(() => {
|
|
53
|
+
if (!mounted) {
|
|
54
|
+
setMounted(true);
|
|
55
|
+
}
|
|
56
|
+
}, [mounted]);
|
|
57
|
+
React.useEffect(() => {
|
|
58
|
+
const player = playRef.current;
|
|
59
|
+
if (!player) return;
|
|
60
|
+
player.seekTo(newProgress);
|
|
61
|
+
}, [newProgress]);
|
|
62
|
+
React.useEffect(() => {
|
|
63
|
+
const container = containerRef.current;
|
|
64
|
+
if (fullScreen === undefined || !container) return;
|
|
65
|
+
fullScreen ? Utils.enterFullScreen(container) : Utils.exitFullScreen();
|
|
66
|
+
}, [fullScreen]);
|
|
67
|
+
React.useEffect(() => {
|
|
68
|
+
setPlaying(forcePlay);
|
|
69
|
+
setStarted(forcePlay);
|
|
70
|
+
}, [forcePlay]);
|
|
71
|
+
const classes = classnames(styles.player, {
|
|
72
|
+
[styles.playerStarted]: started,
|
|
73
|
+
[styles.playerPlaying]: playing
|
|
74
|
+
});
|
|
75
|
+
if (!mounted) return null;
|
|
76
|
+
return _jsxs("div", {
|
|
77
|
+
ref: containerRef,
|
|
78
|
+
className: classes,
|
|
79
|
+
children: [_jsx(ReactPlayer, {
|
|
80
|
+
ref: playRef,
|
|
81
|
+
light: forcePlay ? false : thumb,
|
|
82
|
+
url: url,
|
|
83
|
+
playing: playing,
|
|
84
|
+
muted: muted,
|
|
85
|
+
onClick: updatePlaying,
|
|
86
|
+
onStart: startVideo,
|
|
87
|
+
onProgress: tick,
|
|
88
|
+
onEnded: endVideo,
|
|
89
|
+
progressInterval: 160,
|
|
90
|
+
width: "100%",
|
|
91
|
+
height: "100%",
|
|
92
|
+
className: styles.playerInner,
|
|
93
|
+
...props
|
|
94
|
+
}), !hideCustomControls && _jsx(Controls, {
|
|
95
|
+
started: started,
|
|
96
|
+
playing: playing,
|
|
97
|
+
updatePlaying: updatePlaying,
|
|
98
|
+
progress: progress,
|
|
99
|
+
loadedAmount: loadedAmount,
|
|
100
|
+
setNewProgress: setNewProgress,
|
|
101
|
+
duration: duration,
|
|
102
|
+
currentTime: currentTime,
|
|
103
|
+
fullScreen: fullScreen,
|
|
104
|
+
setFullScreen: setFullScreen,
|
|
105
|
+
muted: muted,
|
|
106
|
+
setMuted: setMuted
|
|
107
|
+
})]
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
export default React.memo(Player);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useUbloContext } from "ublo/with-ublo";
|
|
3
|
+
|
|
4
|
+
const buildClass = className => `${className}--opened`;
|
|
5
|
+
|
|
6
|
+
const useFaq = (selector = ".faq-block", refresh) => {
|
|
7
|
+
const [blocks, setBlocks] = React.useState([]);
|
|
8
|
+
const {
|
|
9
|
+
cmsMode
|
|
10
|
+
} = useUbloContext();
|
|
11
|
+
const clicked = React.useCallback((clickedBlock, blocks) => () => {
|
|
12
|
+
if (cmsMode) return;
|
|
13
|
+
const openedClass = buildClass(clickedBlock.classList[0]);
|
|
14
|
+
blocks.forEach(block => {
|
|
15
|
+
const {
|
|
16
|
+
classList
|
|
17
|
+
} = block;
|
|
18
|
+
block !== clickedBlock || block.classList.contains(openedClass) ? classList.remove(openedClass) : classList.add(openedClass);
|
|
19
|
+
});
|
|
20
|
+
}, [cmsMode]);
|
|
21
|
+
const init = React.useCallback((block, blocks) => {
|
|
22
|
+
block.clicked = clicked(block, blocks);
|
|
23
|
+
block.addEventListener("click", block.clicked);
|
|
24
|
+
}, [clicked]);
|
|
25
|
+
const cleanup = React.useCallback(block => {
|
|
26
|
+
block.removeEventListener("click", block.clicked);
|
|
27
|
+
const openedClass = buildClass(block.classList[0]);
|
|
28
|
+
block.classList.remove(openedClass);
|
|
29
|
+
}, []);
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
if (cmsMode === undefined) {
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
const blocks = Array.from(document.querySelectorAll(selector));
|
|
34
|
+
setBlocks(blocks);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}, [cmsMode, selector, refresh]);
|
|
38
|
+
React.useEffect(() => {
|
|
39
|
+
blocks.forEach(block => init(block, blocks));
|
|
40
|
+
return () => blocks.forEach(cleanup);
|
|
41
|
+
}, [blocks, cleanup, init]);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default useFaq;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useEffect, useState, useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
const ratio = steps => Array.from({
|
|
4
|
+
length: steps + 1
|
|
5
|
+
}).map((_, i) => i / steps);
|
|
6
|
+
|
|
7
|
+
const buildClass = className => `${className}--in-view`;
|
|
8
|
+
|
|
9
|
+
const useInView = (ref, cmsMode, targetedClass, repeat = true, intersectionValue = 0.2) => {
|
|
10
|
+
const [compatible, setCompatible] = useState(false);
|
|
11
|
+
const options = {
|
|
12
|
+
rootMargin: "0px",
|
|
13
|
+
threshold: ratio(100)
|
|
14
|
+
};
|
|
15
|
+
const callback = useCallback(entries => {
|
|
16
|
+
entries.forEach(entry => {
|
|
17
|
+
const {
|
|
18
|
+
intersectionRatio,
|
|
19
|
+
target
|
|
20
|
+
} = entry;
|
|
21
|
+
if (!target || !target.classList[0]) return;
|
|
22
|
+
const inViewClass = buildClass(target.classList[0]);
|
|
23
|
+
|
|
24
|
+
if (intersectionRatio !== 0) {
|
|
25
|
+
if (repeat) {
|
|
26
|
+
intersectionRatio <= intersectionValue ? target.classList.remove(inViewClass) : target.classList.add(inViewClass);
|
|
27
|
+
} else {
|
|
28
|
+
if (intersectionRatio >= intersectionValue) {
|
|
29
|
+
target.classList.add(inViewClass);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
}, [intersectionValue, repeat]);
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
setCompatible(typeof IntersectionObserver !== "undefined");
|
|
37
|
+
}, []);
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (ref.current === undefined) return;
|
|
40
|
+
const targets = targetedClass !== undefined ? Array.from(ref.current.querySelectorAll(targetedClass)) : [ref.current];
|
|
41
|
+
targets.forEach(target => {
|
|
42
|
+
const inViewClass = buildClass(target.classList[0]);
|
|
43
|
+
target.classList.remove(inViewClass);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (cmsMode !== "editing" && cmsMode !== "info" && cmsMode !== "connected") {
|
|
47
|
+
if (!compatible) {
|
|
48
|
+
return targets.forEach(target => {
|
|
49
|
+
const inViewClass = buildClass(target.classList[0]);
|
|
50
|
+
target.classList.add(inViewClass);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const observer = new IntersectionObserver(callback, options);
|
|
55
|
+
|
|
56
|
+
const observe = () => targets.forEach(target => observer?.observe(target));
|
|
57
|
+
|
|
58
|
+
const unobserve = () => {
|
|
59
|
+
targets.forEach(target => {
|
|
60
|
+
if (!target || !target.classList[0]) return;
|
|
61
|
+
const inViewClass = buildClass(target.classList[0]);
|
|
62
|
+
target.classList.remove(inViewClass);
|
|
63
|
+
observer.disconnect();
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
observe();
|
|
68
|
+
return () => unobserve();
|
|
69
|
+
}
|
|
70
|
+
}, [cmsMode, compatible, options, ref, repeat, targetedClass, callback]);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
export default useInView;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useUbloContext } from "ublo/with-ublo";
|
|
3
|
+
|
|
4
|
+
const findAllByKey = (obj, keyToFind) => {
|
|
5
|
+
return Object.entries(obj).reduce((acc, [key, value]) => {
|
|
6
|
+
return key === keyToFind ? acc.concat(value) : typeof value === "object" ? acc.concat(findAllByKey(value, keyToFind)) : acc;
|
|
7
|
+
}, []);
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const useInjectedCmsMarkup = (entries, isReady) => {
|
|
11
|
+
const {
|
|
12
|
+
cmsMode
|
|
13
|
+
} = useUbloContext();
|
|
14
|
+
const injectCmsMarkup = React.useCallback(async () => {
|
|
15
|
+
await window.Cms.register();
|
|
16
|
+
const formats = window.Cms.sectionClasses;
|
|
17
|
+
entries.forEach(entry => {
|
|
18
|
+
const {
|
|
19
|
+
key,
|
|
20
|
+
value
|
|
21
|
+
} = entry;
|
|
22
|
+
const isArray = Array.isArray(value);
|
|
23
|
+
const targets = findAllByKey(formats, key);
|
|
24
|
+
targets.forEach(target => {
|
|
25
|
+
if (!target?.markup) return;
|
|
26
|
+
|
|
27
|
+
if (isArray) {
|
|
28
|
+
const formatedRows = value.map(v => {
|
|
29
|
+
return {
|
|
30
|
+
name: v,
|
|
31
|
+
markup: `<tr><td>${v}</td></tr>`
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
const [anchor] = target.markup;
|
|
35
|
+
target.markup = anchor ? [anchor, ...formatedRows] : formatedRows;
|
|
36
|
+
} else {
|
|
37
|
+
target.markup = value;
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
await window.Cms.analyse();
|
|
42
|
+
}, [entries]);
|
|
43
|
+
React.useEffect(() => {
|
|
44
|
+
if (isReady && cmsMode === "connected") {
|
|
45
|
+
injectCmsMarkup();
|
|
46
|
+
}
|
|
47
|
+
}, [cmsMode, injectCmsMarkup, isReady]);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default useInjectedCmsMarkup;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useUbloContext } from "ublo/with-ublo";
|
|
3
|
+
import PackagesSelector from "../packages-selector";
|
|
4
|
+
import { loadJS } from "../utils/load-js";
|
|
5
|
+
import { openMseM } from "../utils/msem-widget";
|
|
6
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
7
|
+
const preview = typeof window !== "undefined" && window.location.search.indexOf("preview") !== -1;
|
|
8
|
+
|
|
9
|
+
const loadWigetMseM = () => {
|
|
10
|
+
const widgetUrl = "https://widget.msem.tech/static/js/widget-msem.js";
|
|
11
|
+
return loadJS(widgetUrl, "widget-msem");
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const buttonStyles = `
|
|
15
|
+
display: table;
|
|
16
|
+
position: absolute;
|
|
17
|
+
left: 50%;
|
|
18
|
+
bottom: calc(100% + 10px);
|
|
19
|
+
margin: 0px auto;
|
|
20
|
+
padding: 15px;
|
|
21
|
+
color: rgb(255, 255, 255);
|
|
22
|
+
font-family: "Open Sans";
|
|
23
|
+
text-transform: uppercase;
|
|
24
|
+
font-weight: 700;
|
|
25
|
+
background-color: var(--cms-primary-color, #004cc2);
|
|
26
|
+
border-radius: 8px;
|
|
27
|
+
cursor: pointer;
|
|
28
|
+
user-select: none;
|
|
29
|
+
outline: none;
|
|
30
|
+
transform: translateX(-50%);
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
const usePackages = (defaultPopupContent, setPopupContent, cartUrl, selector = "[data-package], .package-lodging__container, .banner-discount") => {
|
|
34
|
+
const {
|
|
35
|
+
cmsMode,
|
|
36
|
+
config
|
|
37
|
+
} = useUbloContext();
|
|
38
|
+
const {
|
|
39
|
+
channel,
|
|
40
|
+
resort
|
|
41
|
+
} = config;
|
|
42
|
+
const onPackageSelection = React.useCallback(link => id => {
|
|
43
|
+
if (!link || !id) return;
|
|
44
|
+
link.setAttribute("data-id", id);
|
|
45
|
+
setPopupContent(defaultPopupContent);
|
|
46
|
+
}, [defaultPopupContent, setPopupContent]);
|
|
47
|
+
const onButtonClick = React.useCallback(e => {
|
|
48
|
+
e.stopPropagation();
|
|
49
|
+
const link = e.target.closest(selector);
|
|
50
|
+
const id = link.getAttribute("data-id");
|
|
51
|
+
setPopupContent({
|
|
52
|
+
title: "Associer un package",
|
|
53
|
+
content: _jsx(PackagesSelector, {
|
|
54
|
+
onSubmit: onPackageSelection(link),
|
|
55
|
+
resort: resort,
|
|
56
|
+
selected: id
|
|
57
|
+
})
|
|
58
|
+
});
|
|
59
|
+
}, [onPackageSelection, resort, selector, setPopupContent]);
|
|
60
|
+
const toggleEditButton = React.useCallback(section => {
|
|
61
|
+
const button = section.querySelector(".package-edit-button");
|
|
62
|
+
|
|
63
|
+
if (cmsMode === "editing" && !button) {
|
|
64
|
+
const newButton = Object.assign(document.createElement("button"), {
|
|
65
|
+
className: "package-edit-button",
|
|
66
|
+
innerHTML: "Associer un package",
|
|
67
|
+
style: buttonStyles
|
|
68
|
+
});
|
|
69
|
+
section.appendChild(newButton);
|
|
70
|
+
newButton.addEventListener("click", onButtonClick);
|
|
71
|
+
} else {
|
|
72
|
+
button && button.removeEventListener("click", onButtonClick);
|
|
73
|
+
button && button.remove();
|
|
74
|
+
}
|
|
75
|
+
}, [cmsMode, onButtonClick]);
|
|
76
|
+
const onPackageClick = React.useCallback(async e => {
|
|
77
|
+
if (cmsMode === "editing") return e.preventDefault();
|
|
78
|
+
const target = e.target.closest(selector);
|
|
79
|
+
const id = target.getAttribute("data-id");
|
|
80
|
+
if (!id || id === "") return;
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
const packageId = parseInt(id);
|
|
83
|
+
const options = {
|
|
84
|
+
resort,
|
|
85
|
+
channel,
|
|
86
|
+
verticalMargin: 100,
|
|
87
|
+
merchant: "",
|
|
88
|
+
packageId,
|
|
89
|
+
catalog: "PUBLIC",
|
|
90
|
+
preview,
|
|
91
|
+
theme: "/theme.css",
|
|
92
|
+
cartUrl
|
|
93
|
+
};
|
|
94
|
+
await openMseM("widgetPackage", options);
|
|
95
|
+
}, [cartUrl, cmsMode, resort, selector]);
|
|
96
|
+
const callback = React.useCallback(e => {
|
|
97
|
+
const {
|
|
98
|
+
section: newSection
|
|
99
|
+
} = e.detail;
|
|
100
|
+
const link = newSection.querySelector(selector);
|
|
101
|
+
|
|
102
|
+
if (link) {
|
|
103
|
+
toggleEditButton(link);
|
|
104
|
+
}
|
|
105
|
+
}, [selector, toggleEditButton]);
|
|
106
|
+
const init = React.useCallback(section => {
|
|
107
|
+
const zone = section.closest(".cms");
|
|
108
|
+
zone.addEventListener("ubloSectionCreated", callback);
|
|
109
|
+
section.addEventListener("click", onPackageClick);
|
|
110
|
+
}, [callback, onPackageClick]);
|
|
111
|
+
const cleanup = React.useCallback(section => {
|
|
112
|
+
const zone = section.closest(".cms");
|
|
113
|
+
zone.removeEventListener("ubloSectionCreated", callback);
|
|
114
|
+
section.removeEventListener("click", onPackageClick);
|
|
115
|
+
}, [callback, onPackageClick]);
|
|
116
|
+
React.useEffect(() => {
|
|
117
|
+
const sections = Array.from(document.querySelectorAll(selector));
|
|
118
|
+
sections.forEach(toggleEditButton);
|
|
119
|
+
sections.forEach(init);
|
|
120
|
+
return () => {
|
|
121
|
+
sections.forEach(cleanup);
|
|
122
|
+
sections.forEach(toggleEditButton);
|
|
123
|
+
};
|
|
124
|
+
}, [cleanup, cmsMode, init, selector, toggleEditButton]);
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export default usePackages;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
const SCROLL_UP = "up";
|
|
3
|
+
const SCROLL_DOWN = "down";
|
|
4
|
+
|
|
5
|
+
const useScrollDirection = ({
|
|
6
|
+
initialDirection,
|
|
7
|
+
thresholdPixels,
|
|
8
|
+
off
|
|
9
|
+
} = {}) => {
|
|
10
|
+
const [scrollDir, setScrollDir] = useState(initialDirection);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const threshold = thresholdPixels || 0;
|
|
13
|
+
let lastScrollY = window.pageYOffset;
|
|
14
|
+
let ticking = false;
|
|
15
|
+
|
|
16
|
+
const updateScrollDir = () => {
|
|
17
|
+
const scrollY = window.pageYOffset;
|
|
18
|
+
|
|
19
|
+
if (Math.abs(scrollY - lastScrollY) < threshold) {
|
|
20
|
+
ticking = false;
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
setScrollDir(scrollY > lastScrollY ? SCROLL_DOWN : SCROLL_UP);
|
|
25
|
+
lastScrollY = scrollY > 0 ? scrollY : 0;
|
|
26
|
+
ticking = false;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const onScroll = () => {
|
|
30
|
+
if (!ticking) {
|
|
31
|
+
window.requestAnimationFrame(updateScrollDir);
|
|
32
|
+
ticking = true;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
!off ? window.addEventListener("scroll", onScroll) : setScrollDir(initialDirection);
|
|
37
|
+
return () => window.removeEventListener("scroll", onScroll);
|
|
38
|
+
}, [initialDirection, thresholdPixels, off]);
|
|
39
|
+
return {
|
|
40
|
+
scrollDir
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default useScrollDirection;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { isValidDate } from "../utils/dates";
|
|
3
|
+
|
|
4
|
+
const useStay = () => {
|
|
5
|
+
const [stay, setStay] = useState();
|
|
6
|
+
useEffect(() => {
|
|
7
|
+
const params = new URLSearchParams(document.location.search);
|
|
8
|
+
const hasAllParams = params.has("from") && params.has("to");
|
|
9
|
+
let stayParams;
|
|
10
|
+
|
|
11
|
+
if (hasAllParams) {
|
|
12
|
+
const from = params.get("from");
|
|
13
|
+
const to = params.get("to");
|
|
14
|
+
const areParamsValid = isValidDate(from) && isValidDate(to);
|
|
15
|
+
|
|
16
|
+
if (areParamsValid) {
|
|
17
|
+
stayParams = {
|
|
18
|
+
from,
|
|
19
|
+
to
|
|
20
|
+
};
|
|
21
|
+
window.sessionStorage.setItem("stay", JSON.stringify(stayParams));
|
|
22
|
+
window.sessionStorage.setItem("Stay", JSON.stringify(stayParams));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const storedStay = window.sessionStorage.getItem("stay");
|
|
27
|
+
setStay(stayParams ? stayParams : storedStay ? JSON.parse(storedStay) : undefined);
|
|
28
|
+
}, []);
|
|
29
|
+
return [stay, setStay];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export default useStay;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
const eventsToBind = {
|
|
3
|
+
document: ["scroll"],
|
|
4
|
+
window: ["resize", "orientationchange"]
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const useSticky = () => {
|
|
8
|
+
const ref = React.useRef(null);
|
|
9
|
+
const [sticky, setSticky] = React.useState(false);
|
|
10
|
+
const observe = React.useCallback(() => {
|
|
11
|
+
const refPageOffset = ref.current.getBoundingClientRect().top;
|
|
12
|
+
const stickyOffset = parseInt(getComputedStyle(ref.current).top);
|
|
13
|
+
const stickyActive = refPageOffset <= stickyOffset;
|
|
14
|
+
setSticky(stickyActive && !sticky || stickyActive && sticky);
|
|
15
|
+
}, [sticky]);
|
|
16
|
+
const init = React.useCallback(() => {
|
|
17
|
+
Object.keys(eventsToBind).forEach(key => {
|
|
18
|
+
const events = eventsToBind[key];
|
|
19
|
+
const target = window[key];
|
|
20
|
+
events.forEach(event => target.addEventListener(event, observe));
|
|
21
|
+
});
|
|
22
|
+
}, [observe]);
|
|
23
|
+
const cleanup = React.useCallback(() => {
|
|
24
|
+
Object.keys(eventsToBind).forEach(key => {
|
|
25
|
+
const events = eventsToBind[key];
|
|
26
|
+
const target = window[key];
|
|
27
|
+
events.forEach(event => target.removeEventListener(event, observe));
|
|
28
|
+
});
|
|
29
|
+
}, [observe]);
|
|
30
|
+
React.useEffect(() => {
|
|
31
|
+
observe();
|
|
32
|
+
init();
|
|
33
|
+
return () => cleanup();
|
|
34
|
+
}, [cleanup, init, observe]);
|
|
35
|
+
return [ref, sticky];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export default useSticky;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { useEffect } from "react";
|
|
2
|
+
import { useUbloContext } from "ublo/with-ublo";
|
|
3
|
+
import * as Plausible from "../plausible";
|
|
4
|
+
import { loadJS } from "../utils/load-js";
|
|
5
|
+
const preview = typeof window !== "undefined" && window.location.search.indexOf("preview") !== -1;
|
|
6
|
+
|
|
7
|
+
const loadWigetMseM = async integration => {
|
|
8
|
+
const source = integration ? "https://widget-integration.msem.tech/static/js/widget-msem.js" : "https://widget.msem.tech/static/js/widget-msem.js";
|
|
9
|
+
await loadJS(source, "widget-msem");
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const useTunnel = (cart, multipleVillages, channel = "ESF", integration) => {
|
|
13
|
+
const {
|
|
14
|
+
lang,
|
|
15
|
+
breadcrumb,
|
|
16
|
+
config
|
|
17
|
+
} = useUbloContext();
|
|
18
|
+
const {
|
|
19
|
+
path: cartUrl
|
|
20
|
+
} = cart;
|
|
21
|
+
const {
|
|
22
|
+
path: esfUrl
|
|
23
|
+
} = multipleVillages ? breadcrumb?.next : breadcrumb;
|
|
24
|
+
const widgetLang = lang === "fr" ? "fr" : "en";
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
const run = async () => {
|
|
27
|
+
await loadWigetMseM(integration);
|
|
28
|
+
|
|
29
|
+
const open = () => window.MseM.tunnel({
|
|
30
|
+
inside: true,
|
|
31
|
+
resort: config.resort,
|
|
32
|
+
channel,
|
|
33
|
+
groundedTo: "#esfplus_container",
|
|
34
|
+
lang: widgetLang,
|
|
35
|
+
preview,
|
|
36
|
+
cartUrl,
|
|
37
|
+
esfUrl,
|
|
38
|
+
fullWidth: true,
|
|
39
|
+
analytics: Plausible.callback,
|
|
40
|
+
useAnalytics: true
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
window.MseM.onLoad(open);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
run();
|
|
47
|
+
}, [cartUrl, channel, config.resort, esfUrl, lang, widgetLang]);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export default useTunnel;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
const DEFAULT_SIZES = {
|
|
3
|
+
width: undefined,
|
|
4
|
+
height: undefined
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const useWindowSizes = () => {
|
|
8
|
+
const [windowSizes, setWindowSizes] = React.useState(DEFAULT_SIZES);
|
|
9
|
+
|
|
10
|
+
const resized = () => {
|
|
11
|
+
setWindowSizes({
|
|
12
|
+
width: window.innerWidth,
|
|
13
|
+
height: window.innerHeight
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
React.useEffect(() => {
|
|
18
|
+
resized();
|
|
19
|
+
const isCompatible = typeof ResizeObserver !== "undefined";
|
|
20
|
+
|
|
21
|
+
if (isCompatible) {
|
|
22
|
+
const observer = new ResizeObserver(resized);
|
|
23
|
+
observer.observe(document.body);
|
|
24
|
+
return () => {
|
|
25
|
+
observer.unobserve(document.body);
|
|
26
|
+
};
|
|
27
|
+
} else {
|
|
28
|
+
window.addEventListener("resize", resized);
|
|
29
|
+
return () => {
|
|
30
|
+
window.removeEventListener("resize", resized);
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
}, []);
|
|
34
|
+
return windowSizes;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export default useWindowSizes;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as React from "react";
|
|
2
|
+
import { useUbloContext } from "ublo/with-ublo";
|
|
3
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
4
|
+
|
|
5
|
+
const extractYoutubeVideoID = url => {
|
|
6
|
+
const regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
|
|
7
|
+
const match = url.match(regExp);
|
|
8
|
+
|
|
9
|
+
if (match && match[7].length === 11) {
|
|
10
|
+
return match[7];
|
|
11
|
+
} else {
|
|
12
|
+
alert("Could not extract video ID.");
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const useYoutubePopup = (selector, setPopupContent) => {
|
|
17
|
+
const {
|
|
18
|
+
cmsMode
|
|
19
|
+
} = useUbloContext();
|
|
20
|
+
const openVideo = React.useCallback(e => {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
const link = e.target.closest("a")?.getAttribute("href")?.trim();
|
|
23
|
+
if (!link) return;
|
|
24
|
+
const code = extractYoutubeVideoID(link);
|
|
25
|
+
const url = `https://www.youtube.com/embed/${code}`;
|
|
26
|
+
setPopupContent({
|
|
27
|
+
content: _jsx("div", {
|
|
28
|
+
style: {
|
|
29
|
+
position: "relative",
|
|
30
|
+
width: 1200,
|
|
31
|
+
maxWidth: "100%",
|
|
32
|
+
paddingTop: "56.25%"
|
|
33
|
+
},
|
|
34
|
+
children: _jsx("iframe", {
|
|
35
|
+
style: {
|
|
36
|
+
position: "absolute",
|
|
37
|
+
top: 0,
|
|
38
|
+
left: 0,
|
|
39
|
+
width: "100%",
|
|
40
|
+
height: "100%"
|
|
41
|
+
},
|
|
42
|
+
src: url,
|
|
43
|
+
frameBorder: "0",
|
|
44
|
+
allowFullScreen: true,
|
|
45
|
+
title: ""
|
|
46
|
+
})
|
|
47
|
+
}),
|
|
48
|
+
popupClassName: "iframe-video"
|
|
49
|
+
});
|
|
50
|
+
}, [setPopupContent]);
|
|
51
|
+
const init = React.useCallback(video => video.addEventListener("click", openVideo), [openVideo]);
|
|
52
|
+
const cleanup = React.useCallback(video => video.removeEventListener("click", openVideo), [openVideo]);
|
|
53
|
+
React.useEffect(() => {
|
|
54
|
+
if (cmsMode !== "editing") {
|
|
55
|
+
const videos = Array.from(document.querySelectorAll(selector));
|
|
56
|
+
videos.forEach(init);
|
|
57
|
+
return () => videos.forEach(cleanup);
|
|
58
|
+
}
|
|
59
|
+
}, [cleanup, cmsMode, init, selector]);
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export default useYoutubePopup;
|