crabfit/crabfit-frontend/src/components/OutlookCalendar/OutlookCalendar.tsx
2021-06-03 16:44:22 +10:00

239 lines
7 KiB
TypeScript

import { useState, useEffect } from 'react';
import { PublicClientApplication } from "@azure/msal-browser";
import { Client } from "@microsoft/microsoft-graph-client";
import { useTranslation } from 'react-i18next';
import { Button, Center } from 'components';
import { Loader } from '../Loading/loadingStyle';
import {
LoginButton,
CalendarList,
CheckboxInput,
CheckboxLabel,
CalendarLabel,
Info,
Options,
Title,
Icon,
} from '../GoogleCalendar/googleCalendarStyle';
import outlookLogo from 'res/outlook.svg';
const scopes = ['Calendars.Read', 'Calendars.Read.Shared'];
// Initialise the MSAL object
const publicClientApplication = new PublicClientApplication({
auth: {
clientId: '5d1ab8af-1ba3-4b79-b033-b0ee509c2be6',
redirectUri: process.env.NODE_ENV === 'production' ? 'https://crab.fit' : 'http://localhost:3000',
},
cache: {
cacheLocation: 'sessionStorage',
storeAuthStateInCookie: true,
},
});
const getAuthenticatedClient = accessToken => {
const client = Client.init({
authProvider: done => done(null, accessToken),
});
return client;
};
const OutlookCalendar = ({ timeZone, timeMin, timeMax, onImport }) => {
const [client, setClient] = useState(undefined);
const [calendars, setCalendars] = useState(undefined);
const [freeBusyLoading, setFreeBusyLoading] = useState(false);
const { t } = useTranslation('event');
const checkLogin = async () => {
const accounts = publicClientApplication.getAllAccounts();
if (accounts && accounts.length > 0) {
try {
const accessToken = await getAccessToken();
setClient(getAuthenticatedClient(accessToken));
} catch (e) {
console.error(e);
signOut();
}
} else {
setClient(null);
}
};
const signIn = async () => {
try {
await publicClientApplication.loginPopup({ scopes });
} catch (e) {
console.error(e);
} finally {
checkLogin();
}
};
const signOut = async () => {
try {
await publicClientApplication.logoutRedirect({
onRedirectNavigate: () => false,
});
} catch (e) {
console.error(e);
} finally {
checkLogin();
}
};
const getAccessToken = async () => {
try {
const accounts = publicClientApplication.getAllAccounts();
if (accounts.length <= 0) throw new Error('login_required');
// Try to get silently
const result = await publicClientApplication.acquireTokenSilent({
scopes,
account: accounts[0],
});
return result.accessToken;
} catch (e) {
if ([
'consent_required',
'interaction_required',
'login_required',
'no_account_in_silent_request'
].includes(e.message)) {
// Try to get with popup
const result = await publicClientApplication.acquireTokenPopup({ scopes });
return result.accessToken;
} else {
throw e;
}
}
};
const importAvailability = () => {
setFreeBusyLoading(true);
gtag('event', 'outlook_cal_sync', {
'event_category': 'event',
});
client.api('/me/calendar/getSchedule').post({
schedules: calendars.filter(c => c.checked).map(c => c.id),
startTime: {
dateTime: timeMin,
timeZone,
},
endTime: {
dateTime: timeMax,
timeZone,
},
availabilityViewInterval: 30,
})
.then(response => {
onImport(response.value.reduce((busy, c) => c.hasOwnProperty('error') ? busy : [...busy, ...c.scheduleItems.filter(item => item.status === 'busy' || item.status === 'tentative')], []));
})
.catch(e => {
console.error(e);
signOut();
})
.finally(() => setFreeBusyLoading(false));
};
useEffect(() => checkLogin(), []);
useEffect(() => {
if (client) {
client.api('/me/calendars').get()
.then(response => {
setCalendars(response.value.map(item => ({
'name': item.name,
'description': item.owner.name,
'id': item.owner.address,
'color': item.hexColor,
'checked': item.isDefaultCalendar === true,
})));
})
.catch(e => {
console.error(e);
signOut();
});
}
}, [client]);
return (
<>
{!client ? (
<Center>
<Button
onClick={() => signIn()}
isLoading={client === undefined}
buttonWidth={`${Math.max(t('event:you.outlook_cal').length*10, 270)}px`}
primaryColor="#0364B9"
secondaryColor="#02437D">
<LoginButton>
<img src={outlookLogo} alt="" />
<span>{t('event:you.outlook_cal')}</span>
</LoginButton>
</Button>
</Center>
) : (
<CalendarList>
<Title>
<Icon src={outlookLogo} alt="" />
{/* eslint-disable-next-line */}
<strong>{t('event:you.outlook_cal')}</strong>
(<a href="#" onClick={e => {
e.preventDefault();
signOut();
}}>{t('event:you.google_cal.logout')}</a>)
</Title>
<Options>
{calendars !== undefined && !calendars.every(c => c.checked) && (
/* eslint-disable-next-line */
<a href="#" onClick={e => {
e.preventDefault();
setCalendars(calendars.map(c => ({...c, checked: true})));
}}>{t('event:you.google_cal.select_all')}</a>
)}
{calendars !== undefined && calendars.every(c => c.checked) && (
/* eslint-disable-next-line */
<a href="#" onClick={e => {
e.preventDefault();
setCalendars(calendars.map(c => ({...c, checked: false})));
}}>{t('event:you.google_cal.select_none')}</a>
)}
</Options>
{calendars !== undefined ? calendars.map(calendar => (
<div key={calendar.id}>
<CheckboxInput
type="checkbox"
role="checkbox"
id={calendar.id}
color={calendar.color}
checked={calendar.checked}
onChange={e => setCalendars(calendars.map(c => c.id === calendar.id ? {...c, checked: !c.checked} : c))}
/>
<CheckboxLabel htmlFor={calendar.id} color={calendar.color} />
<CalendarLabel htmlFor={calendar.id}>{calendar.name}</CalendarLabel>
</div>
)) : (
<Loader />
)}
{calendars !== undefined && (
<>
<Info>{t('event:you.google_cal.info')}</Info>
<Button
buttonWidth="170px"
buttonHeight="35px"
isLoading={freeBusyLoading}
disabled={freeBusyLoading}
onClick={() => importAvailability()}
>{t('event:you.google_cal.button')}</Button>
</>
)}
</CalendarList>
)}
</>
);
};
export default OutlookCalendar;