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 {content: t`tour.creator.step6`, target: '.tour_car_add2'},
61 ].map(step => ({...step, ...STEP_SETTINGS}))
62 : [
63 {content: t`tour.user.step1`, target: '.tour_car_add2'},
64 {content: t`tour.user.step2`, target: '.tour_car_add1'},
65 {content: t`tour.user.step3`, target: '.tour_waiting_list'},
66 {content: t`tour.user.step4`, target: '.tour_event_infos'},
67 {content: t`tour.user.step5`, target: '.tour_event_share'},
68 ].map(step => ({...step, ...STEP_SETTINGS}));
69 }, [isCreator]);
70
71 // Init tour
72 useEffect(() => {
73 const hasOnboarded = () => {
74 if (isCreator) return profile?.onboardingCreator || onboardingCreator;
75 else return profile?.onboardingUser || onboardingUser;
76 };
77 if (!hasOnboarded() && steps.length > 0) setTour({showWelcome: true});
78 else setTour({showWelcome: false});
79 }, [steps, isCreator, onboardingCreator, onboardingUser, profile]);
80
81 // On step change : wait for the UI a little and run it
82 useEffect(() => {
83 let timer;
84 if (step >= 0 && step !== prev)
85 timer = setTimeout(() => setTour({run: true}), 250);
86 return () => clearTimeout(timer);
87 }, [step]);
88
89 const onFinish = () => {
90 if (profile) {
91 if (isCreator && !profile.onboardingCreator)
92 updateProfile({variables: {userUpdate: {onboardingCreator: true}}});
93 else if (!isCreator && !profile.onboardingUser)
94 updateProfile({variables: {userUpdate: {onboardingUser: true}}});
95 } else {
96 if (isCreator && !onboardingCreator)
97 setOnboarding({onboardingCreator: true});
98 else if (!isCreator && !onboardingUser)
99 setOnboarding({onboardingUser: true});
100 }
101 };
102
103 const onTourRestart = () => {
104 setTour({showWelcome: true});
105 };
106
107 const onTourChange = (data: CallBackProps) => {
108 const {action, index, type, status} = data;
109
110 if (
111 ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND] as string[]).includes(type)
112 ) {
113 if (action === ACTIONS.CLOSE) {
114 setTour({run: false, step: -1, prev: -1});
115 } else {
116 setTour({
117 run: false,
118 step: index + (action === ACTIONS.PREV ? -1 : 1),
119 prev: index,
120 });
121 }
122 } else if (
123 ([STATUS.FINISHED, STATUS.SKIPPED] as string[]).includes(status)
124 ) {
125 setTour({run: false, step: -1, prev: -1});
126 if (status === STATUS.FINISHED) onFinish();
127 }
128 };
129
130 return {
131 run,
132 steps,
133 step,
134 onTourChange,
135 onTourRestart,
136 };
137};
138
139export default useTour;