frontend/hooks/useTour.ts (view raw)
1import {useEffect, useMemo} from 'react';
2import {useTranslation} from 'react-i18next';
3import {CallBackProps, STATUS, EVENTS, ACTIONS} from 'react-joyride';
4import {useUpdateMeMutation} from '../generated/graphql';
5import useOnboardingStore from '../stores/useOnboardingStore';
6import useTourStore from '../stores/useTourStore';
7import useEventStore from '../stores/useEventStore';
8import useAddToEvents from '../hooks/useAddToEvents';
9import useProfile from './useProfile';
10
11const STEP_SETTINGS = {
12 disableBeacon: true,
13 disableOverlayClose: true,
14 hideCloseButton: true,
15 hideFooter: false,
16 spotlightClicks: false,
17 showSkipButton: true,
18 styles: {
19 options: {
20 zIndex: 10000,
21 },
22 },
23};
24
25const useTour = () => {
26 const {t} = useTranslation();
27 const isCreator = useTourStore(s => s.isCreator);
28 const run = useTourStore(s => s.run);
29 const step = useTourStore(s => s.step);
30 const prev = useTourStore(s => s.prev);
31 const setTour = useTourStore(s => s.setTour);
32 const onboardingUser = useOnboardingStore(s => s.onboardingUser);
33 const onboardingCreator = useOnboardingStore(s => s.onboardingCreator);
34 const setOnboarding = useOnboardingStore(s => s.setOnboarding);
35 const {profile, isReady} = useProfile();
36 const event = useEventStore(s => s.event);
37 const {eventsToBeAdded} = useAddToEvents();
38 const [updateProfile] = useUpdateMeMutation();
39
40 // Check if user is the event creator
41 useEffect(() => {
42 if (!isReady || !event) return;
43
44 let isCreator = eventsToBeAdded.includes(event?.id);
45 if (profile) isCreator = profile.events.map(e => e.id).includes(event?.id);
46
47 setTour({isCreator});
48 }, [isReady, event, eventsToBeAdded, profile]);
49
50 const steps = useMemo(() => {
51 if (isCreator === null) return [];
52 return isCreator
53 ? [
54 {content: t`tour.creator.step1`, target: '.tour_event_infos'},
55 {content: t`tour.creator.step2`, target: '.tour_event_edit'},
56 {content: t`tour.creator.step3`, target: '.tour_event_share'},
57 {content: t`tour.creator.step4`, target: '.tour_waiting_list'},
58 {content: t`tour.creator.step5`, target: '.tour_travel_add'},
59 ].map(step => ({...step, ...STEP_SETTINGS}))
60 : [
61 {content: t`tour.user.step1`, target: '.tour_travel_add'},
62 {content: t`tour.user.step2`, target: '.tour_waiting_list'},
63 {content: t`tour.user.step3`, target: '.tour_event_infos'},
64 {content: t`tour.user.step4`, target: '.tour_event_share'},
65 ].map(step => ({...step, ...STEP_SETTINGS}));
66 }, [isCreator]);
67
68 // Init tour
69 useEffect(() => {
70 if (!isReady) return;
71 let hasOnboarded = profile?.onboardingUser || onboardingUser;
72 if (isCreator)
73 hasOnboarded = profile?.onboardingCreator || onboardingCreator;
74 const hasSteps = steps.length > 0;
75 const showWelcome = hasSteps && !hasOnboarded;
76 setTour({showWelcome});
77 }, [steps, isCreator, onboardingCreator, onboardingUser, profile, isReady]);
78
79 // On step change : wait for the UI a little and run it
80 useEffect(() => {
81 let timer;
82 if (step >= 0 && step !== prev)
83 timer = setTimeout(() => setTour({run: true}), 250);
84 return () => clearTimeout(timer);
85 }, [step]);
86
87 const onFinish = () => {
88 if (profile) {
89 if (isCreator && !profile.onboardingCreator)
90 updateProfile({variables: {userUpdate: {onboardingCreator: true}}});
91 else if (!isCreator && !profile.onboardingUser)
92 updateProfile({variables: {userUpdate: {onboardingUser: true}}});
93 } else {
94 if (isCreator && !onboardingCreator)
95 setOnboarding({onboardingCreator: true});
96 else if (!isCreator && !onboardingUser)
97 setOnboarding({onboardingUser: true});
98 }
99 };
100
101 const onTourChange = (data: CallBackProps) => {
102 const {action, index, type, status} = data;
103
104 if (
105 ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND] as string[]).includes(type)
106 ) {
107 if (action === ACTIONS.CLOSE) {
108 setTour({run: false, step: -1, prev: -1});
109 } else {
110 setTour({
111 run: false,
112 step: index + (action === ACTIONS.PREV ? -1 : 1),
113 prev: index,
114 });
115 }
116 } else if (
117 ([STATUS.FINISHED, STATUS.SKIPPED] as string[]).includes(status)
118 ) {
119 setTour({run: false, step: -1, prev: -1});
120 if (status === STATUS.FINISHED) onFinish();
121 }
122 };
123
124 return {
125 run,
126 steps,
127 step,
128 onTourChange,
129 };
130};
131
132export default useTour;