IA-1354 move oauth token logic to Redux
[ProtonMail-WebClient.git] / packages / calendar / components / zoomIntegration / ZoomRow.tsx
blob56e201445860a92f97e950bc73d0d08ccc13d458
1 import { useEffect, useState } from 'react';
3 import { c } from 'ttag';
5 import { useUser } from '@proton/account/user/hooks';
6 import { useOAuthToken } from '@proton/activation';
7 import { createToken } from '@proton/activation/src/api';
8 import useOAuthPopup from '@proton/activation/src/hooks/useOAuthPopup';
9 import type { OAuthProps } from '@proton/activation/src/interface';
10 import { EASY_SWITCH_SOURCES, ImportType, OAUTH_PROVIDER } from '@proton/activation/src/interface';
11 import { Button, CircleLoader } from '@proton/atoms';
12 import { Icon, IconRow, useApi } from '@proton/components';
13 import { useLoading } from '@proton/hooks';
14 import { createZoomMeeting } from '@proton/shared/lib/api/calendars';
15 import type { EventModel, VideoConferenceMeetingCreation } from '@proton/shared/lib/interfaces/calendar';
16 import zoomLogo from '@proton/styles/assets/img/video-conferencing/zoom.svg';
17 import clsx from '@proton/utils/clsx';
19 import { VideoConferencingWidget } from '../videoConferencing/VideoConferencingWidget';
20 import { VIDEO_CONF_SERVICES } from '../videoConferencing/constants';
22 type ZoomIntegrationState = 'loadingConfig' | 'disconnected' | 'connected' | 'loading' | 'meeting-present';
24 const getIcon = (state: ZoomIntegrationState) => {
25     switch (state) {
26         case 'disconnected':
27         case 'connected':
28             return <img src={zoomLogo} className="h-6 w-6 hidden" alt="" />;
29         case 'meeting-present':
30             return <img src={zoomLogo} className="h-6 w-6" alt="" />;
31         case 'loading':
32         case 'loadingConfig':
33             return <CircleLoader className="color-primary h-4 w-4" />;
34     }
37 interface Props {
38     model: EventModel;
39     setModel: (value: EventModel) => void;
42 export const ZoomRow = ({ model, setModel }: Props) => {
43     const [user] = useUser();
44     const [oAuthToken, oauthTokenLoading] = useOAuthToken();
45     const isUserConnectedToZoom = oAuthToken?.some(({ Provider }) => Provider === OAUTH_PROVIDER.ZOOM);
47     const [processState, setProcessState] = useState<ZoomIntegrationState>('loadingConfig');
49     const api = useApi();
50     const [, withLoading] = useLoading();
51     const { triggerOAuthPopup, loadingConfig } = useOAuthPopup({
52         errorMessage: c('Error').t`Failed to load oauth modal.`,
53     });
55     useEffect(() => {
56         if (loadingConfig || oauthTokenLoading) {
57             setProcessState('loadingConfig');
58         } else {
59             setProcessState(isUserConnectedToZoom ? 'connected' : 'disconnected');
60         }
61     }, [loadingConfig, oauthTokenLoading]);
63     useEffect(() => {
64         if (model.conferenceUrl) {
65             setProcessState('meeting-present');
66         }
67     }, []);
69     const createVideoConferenceMeeting = async () => {
70         setProcessState('loading');
71         const data = await withLoading(api<VideoConferenceMeetingCreation>(createZoomMeeting()));
73         setModel({
74             ...model,
75             conferenceId: data?.VideoConference?.ID,
76             conferenceUrl: data?.VideoConference?.URL,
77             conferencePasscode: data?.VideoConference?.Password,
78             conferenceCreator: user.ID,
79         });
80         setProcessState('meeting-present');
81     };
83     const handleClick = async () => {
84         if (user.isFree) {
85             // TODO display upsell for Zoom, will be done in a separate MR
86             alert('Display upsell for zoom');
87             return;
88         }
90         if (processState === 'disconnected') {
91             triggerOAuthPopup({
92                 provider: OAUTH_PROVIDER.ZOOM,
93                 scope: '',
94                 callback: async (oAuthProps: OAuthProps) => {
95                     const { Code, Provider, RedirectUri } = oAuthProps;
97                     await api(
98                         createToken({
99                             Provider,
100                             Code,
101                             RedirectUri,
102                             Source: EASY_SWITCH_SOURCES.CALENDAR_WEB_CREATE_EVENT,
103                             Products: [ImportType.CALENDAR],
104                         })
105                     );
106                     await createVideoConferenceMeeting();
107                 },
108             });
109         } else {
110             await createVideoConferenceMeeting();
111         }
112     };
114     if (processState === 'meeting-present') {
115         return (
116             <VideoConferencingWidget
117                 location="calendar"
118                 data={{
119                     service: VIDEO_CONF_SERVICES.ZOOM,
120                     meetingId: model.conferenceId,
121                     meetingUrl: model.conferenceUrl,
122                     password: model.conferencePasscode,
123                 }}
124             />
125         );
126     }
128     return (
129         <IconRow icon={getIcon(processState)} labelClassName={clsx(processState === 'loading' && 'my-auto p-0')}>
130             {(processState === 'connected' || processState === 'disconnected') && (
131                 <div className="flex items-center gap-1">
132                     <Button
133                         onClick={handleClick}
134                         disabled={loadingConfig || oauthTokenLoading}
135                         loading={loadingConfig || oauthTokenLoading}
136                         shape="underline"
137                         className="p-0"
138                         color="norm"
139                         size="small"
140                     >
141                         {c('Zoom integration').t`Add Zoom meeting`}
142                     </Button>
143                     {user.isFree && <Icon name="upgrade" className="color-primary" />}
144                 </div>
145             )}
147             {(processState === 'loading' || processState === 'loadingConfig') && (
148                 <Button disabled shape="ghost" className="p-0" color="norm" size="small">
149                     {loadingConfig
150                         ? c('Zoom integration').t`Loading Zoom configuration`
151                         : c('Zoom integration').t`Adding conferencing details`}
152                 </Button>
153             )}
154         </IconRow>
155     );