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, notReady} = 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 (notReady || !event) return;
43
44 if (profile) {
45 setTour({isCreator: profile.events.map(e => e.id).includes(event?.id)});
46 } else {
47 setTour({isCreator: eventsToBeAdded.includes(event?.id)});
48 }
49 }, [notReady, event, eventsToBeAdded, profile]);
50
51 const steps = useMemo(() => {
52 if (isCreator === null) return [];
53 return isCreator
54 ? [
55 {content: t`tour.creator.step1`, target: '.tour_event_infos'},
56 {content: t`tour.creator.step2`, target: '.tour_event_edit'},
57 {content: t`tour.creator.step3`, target: '.tour_event_share'},
58 {content: t`tour.creator.step4`, target: '.tour_waiting_list'},
59 {content: t`tour.creator.step5`, target: '.tour_car_add1'},
60 ].map(step => ({...step, ...STEP_SETTINGS}))
61 : [
62 {content: t`tour.user.step1`, target: '.tour_car_add1'},
63 {content: t`tour.user.step2`, target: '.tour_waiting_list'},
64 {content: t`tour.user.step3`, target: '.tour_event_infos'},
65 {content: t`tour.user.step4`, target: '.tour_event_share'},
66 ].map(step => ({...step, ...STEP_SETTINGS}));
67 }, [isCreator]);
68
69 // Init tour
70 useEffect(() => {
71 const hasOnboarded = () => {
72 if (isCreator) return profile?.onboardingCreator || onboardingCreator;
73 else return profile?.onboardingUser || onboardingUser;
74 };
75 if (!hasOnboarded() && steps.length > 0) setTour({showWelcome: true});
76 else setTour({showWelcome: false});
77 }, [steps, isCreator, onboardingCreator, onboardingUser, profile]);
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;