Outlook calendar sync
This commit is contained in:
parent
2d9f98eda5
commit
e03ddf6814
|
|
@ -3,6 +3,7 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@azure/msal-browser": "^2.14.2",
|
||||||
"@emotion/react": "^11.1.5",
|
"@emotion/react": "^11.1.5",
|
||||||
"@emotion/styled": "^11.1.5",
|
"@emotion/styled": "^11.1.5",
|
||||||
"@microsoft/microsoft-graph-client": "^2.2.1",
|
"@microsoft/microsoft-graph-client": "^2.2.1",
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,7 @@
|
||||||
"select_none": "Select none",
|
"select_none": "Select none",
|
||||||
"info": "Importing will overwrite your current availability",
|
"info": "Importing will overwrite your current availability",
|
||||||
"button": "Import availability"
|
"button": "Import availability"
|
||||||
}
|
},
|
||||||
|
"outlook_cal": "Sync with Outlook Calendar"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ import dayjs from 'dayjs';
|
||||||
import localeData from 'dayjs/plugin/localeData';
|
import localeData from 'dayjs/plugin/localeData';
|
||||||
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
import customParseFormat from 'dayjs/plugin/customParseFormat';
|
||||||
import isBetween from 'dayjs/plugin/isBetween';
|
import isBetween from 'dayjs/plugin/isBetween';
|
||||||
|
import dayjs_timezone from 'dayjs/plugin/timezone';
|
||||||
|
import utc from 'dayjs/plugin/utc';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Wrapper,
|
Wrapper,
|
||||||
|
|
@ -22,11 +24,13 @@ import {
|
||||||
} from 'components/AvailabilityViewer/availabilityViewerStyle';
|
} from 'components/AvailabilityViewer/availabilityViewerStyle';
|
||||||
import { Time } from './availabilityEditorStyle';
|
import { Time } from './availabilityEditorStyle';
|
||||||
|
|
||||||
import { GoogleCalendar, Center } from 'components';
|
import { GoogleCalendar, OutlookCalendar, Center } from 'components';
|
||||||
|
|
||||||
dayjs.extend(localeData);
|
dayjs.extend(localeData);
|
||||||
dayjs.extend(customParseFormat);
|
dayjs.extend(customParseFormat);
|
||||||
dayjs.extend(isBetween);
|
dayjs.extend(isBetween);
|
||||||
|
dayjs.extend(utc);
|
||||||
|
dayjs.extend(dayjs_timezone);
|
||||||
|
|
||||||
const AvailabilityEditor = ({
|
const AvailabilityEditor = ({
|
||||||
times,
|
times,
|
||||||
|
|
@ -63,16 +67,28 @@ const AvailabilityEditor = ({
|
||||||
</StyledMain>
|
</StyledMain>
|
||||||
{isSpecificDates && (
|
{isSpecificDates && (
|
||||||
<StyledMain>
|
<StyledMain>
|
||||||
<GoogleCalendar
|
<div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', justifyContent: 'center', gap: 12 }}>
|
||||||
timeMin={dayjs(times[0], 'HHmm-DDMMYYYY').toISOString()}
|
<GoogleCalendar
|
||||||
timeMax={dayjs(times[times.length-1], 'HHmm-DDMMYYYY').add(15, 'm').toISOString()}
|
timeMin={dayjs(times[0], 'HHmm-DDMMYYYY').toISOString()}
|
||||||
timeZone={timezone}
|
timeMax={dayjs(times[times.length-1], 'HHmm-DDMMYYYY').add(15, 'm').toISOString()}
|
||||||
onImport={busyArray => onChange(
|
timeZone={timezone}
|
||||||
times.filter(time => !busyArray.some(busy =>
|
onImport={busyArray => onChange(
|
||||||
dayjs(time, 'HHmm-DDMMYYYY').isBetween(busy.start, busy.end, null, '[)')
|
times.filter(time => !busyArray.some(busy =>
|
||||||
))
|
dayjs(time, 'HHmm-DDMMYYYY').isBetween(busy.start, busy.end, null, '[)')
|
||||||
)}
|
))
|
||||||
/>
|
)}
|
||||||
|
/>
|
||||||
|
<OutlookCalendar
|
||||||
|
timeMin={dayjs(times[0], 'HHmm-DDMMYYYY').toISOString()}
|
||||||
|
timeMax={dayjs(times[times.length-1], 'HHmm-DDMMYYYY').add(15, 'm').toISOString()}
|
||||||
|
timeZone={timezone}
|
||||||
|
onImport={busyArray => onChange(
|
||||||
|
times.filter(time => !busyArray.some(busy =>
|
||||||
|
dayjs(time, 'HHmm-DDMMYYYY').isBetween(dayjs.tz(busy.start.dateTime, busy.start.timeZone), dayjs.tz(busy.end.dateTime, busy.end.timeZone), null, '[)')
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</StyledMain>
|
</StyledMain>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,8 @@ import {
|
||||||
CalendarLabel,
|
CalendarLabel,
|
||||||
Info,
|
Info,
|
||||||
Options,
|
Options,
|
||||||
|
Title,
|
||||||
|
Icon,
|
||||||
} from './googleCalendarStyle';
|
} from './googleCalendarStyle';
|
||||||
|
|
||||||
import googleLogo from 'res/google.svg';
|
import googleLogo from 'res/google.svg';
|
||||||
|
|
@ -109,13 +111,15 @@ const GoogleCalendar = ({ timeZone, timeMin, timeMax, onImport }) => {
|
||||||
</Center>
|
</Center>
|
||||||
) : (
|
) : (
|
||||||
<CalendarList>
|
<CalendarList>
|
||||||
<p>
|
<Title>
|
||||||
|
<Icon src={googleLogo} alt="" />
|
||||||
{/* eslint-disable-next-line */}
|
{/* eslint-disable-next-line */}
|
||||||
<strong>{t('event:you.google_cal.login')}</strong> (<a href="#" onClick={e => {
|
<strong>{t('event:you.google_cal.login')}</strong>
|
||||||
|
(<a href="#" onClick={e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
signOut();
|
signOut();
|
||||||
}}>{t('event:you.google_cal.logout')}</a>)
|
}}>{t('event:you.google_cal.logout')}</a>)
|
||||||
</p>
|
</Title>
|
||||||
<Options>
|
<Options>
|
||||||
{calendars !== undefined && !calendars.every(c => c.checked) && (
|
{calendars !== undefined && !calendars.every(c => c.checked) && (
|
||||||
/* eslint-disable-next-line */
|
/* eslint-disable-next-line */
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export const LoginButton = styled.div`
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const CalendarList = styled.div`
|
export const CalendarList = styled.div`
|
||||||
|
width: 100%;
|
||||||
& > div {
|
& > div {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin: 2px 0;
|
margin: 2px 0;
|
||||||
|
|
@ -110,3 +111,18 @@ export const Options = styled.div`
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 0 0 5px;
|
padding: 0 0 5px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
export const Title = styled.p`
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
& strong {
|
||||||
|
margin-right: 1ex;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const Icon = styled.img`
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
margin-right: 12px;
|
||||||
|
`;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,240 @@
|
||||||
|
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';
|
||||||
|
|
||||||
|
// Initialise the MSAL object
|
||||||
|
const publicClientApplication = new PublicClientApplication({
|
||||||
|
auth: {
|
||||||
|
clientId: '78739601-9834-4d41-a281-74ca2a50b2e6',
|
||||||
|
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: ['Calendars.Read', 'Calendars.Read.Shared'],
|
||||||
|
});
|
||||||
|
} 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: ['Calendars.Read', 'Calendars.Read.Shared'],
|
||||||
|
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: ['Calendars.Read', 'Calendars.Read.Shared'],
|
||||||
|
});
|
||||||
|
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;
|
||||||
|
|
@ -11,6 +11,7 @@ export { default as AvailabilityEditor } from './AvailabilityEditor/Availability
|
||||||
export { default as Error } from './Error/Error';
|
export { default as Error } from './Error/Error';
|
||||||
export { default as Loading } from './Loading/Loading';
|
export { default as Loading } from './Loading/Loading';
|
||||||
export { default as GoogleCalendar } from './GoogleCalendar/GoogleCalendar';
|
export { default as GoogleCalendar } from './GoogleCalendar/GoogleCalendar';
|
||||||
|
export { default as OutlookCalendar } from './OutlookCalendar/OutlookCalendar';
|
||||||
|
|
||||||
export { default as Center } from './Center/Center';
|
export { default as Center } from './Center/Center';
|
||||||
export { default as Donate } from './Donate/Donate';
|
export { default as Donate } from './Donate/Donate';
|
||||||
|
|
|
||||||
54
crabfit-frontend/src/res/outlook.svg
Normal file
54
crabfit-frontend/src/res/outlook.svg
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 15.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||||
|
xmlns:cc="http://creativecommons.org/ns#"
|
||||||
|
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
version="1.1"
|
||||||
|
id="Layer_1"
|
||||||
|
x="0px"
|
||||||
|
y="0px"
|
||||||
|
width="103.17322"
|
||||||
|
height="104.31332"
|
||||||
|
viewBox="0 0 103.17322 104.31332"
|
||||||
|
enable-background="new 0 0 190 50"
|
||||||
|
xml:space="preserve"
|
||||||
|
inkscape:version="0.48.2 r9819"
|
||||||
|
sodipodi:docname="Outlook_logo.svg"><metadata
|
||||||
|
id="metadata45"><rdf:RDF><cc:Work
|
||||||
|
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||||
|
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||||
|
id="defs43" />
|
||||||
|
<path
|
||||||
|
d="m 64.566509,22.116383 v 20.404273 l 7.130526,4.489881 c 0.188058,0.05485 0.595516,0.05877 0.783574,0 L 103.16929,26.320259 c 0,-2.44867 -2.28412,-4.203876 -3.573094,-4.203876 H 64.566509 z"
|
||||||
|
id="path3"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#FFFFFF" />
|
||||||
|
<path
|
||||||
|
d="m 64.566509,50.13308 6.507584,4.470291 c 0.916782,0.673874 2.021622,0 2.021622,0 -1.100922,0.673874 30.077495,-20.035993 30.077495,-20.035993 v 37.501863 c 0,4.082422 -2.61322,5.794531 -5.551621,5.794531 H 64.562591 V 50.13308 z"
|
||||||
|
id="path5"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#FFFFFF" />
|
||||||
|
|
||||||
|
|
||||||
|
<g
|
||||||
|
id="g23"
|
||||||
|
transform="matrix(3.9178712,0,0,3.9178712,-13.481403,-41.384473)">
|
||||||
|
<path
|
||||||
|
d="m 11.321,20.958 c -0.566,0 -1.017,0.266 -1.35,0.797 -0.333,0.531 -0.5,1.234 -0.5,2.109 0,0.888 0.167,1.59 0.5,2.106 0.333,0.517 0.77,0.774 1.31,0.774 0.557,0 0.999,-0.251 1.325,-0.753 0.326,-0.502 0.49,-1.199 0.49,-2.09 0,-0.929 -0.158,-1.652 -0.475,-2.169 -0.317,-0.516 -0.75,-0.774 -1.3,-0.774 z"
|
||||||
|
id="path25"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#FFFFFF" />
|
||||||
|
<path
|
||||||
|
d="m 3.441,13.563 v 20.375 l 15.5,3.25 V 10.563 l -15.5,3 z m 10.372,13.632 c -0.655,0.862 -1.509,1.294 -2.563,1.294 -1.027,0 -1.863,-0.418 -2.51,-1.253 C 8.094,26.4 7.77,25.312 7.77,23.97 c 0,-1.417 0.328,-2.563 0.985,-3.438 0.657,-0.875 1.527,-1.313 2.61,-1.313 1.023,0 1.851,0.418 2.482,1.256 0.632,0.838 0.948,1.942 0.948,3.313 10e-4,1.409 -0.327,2.545 -0.982,3.407 z"
|
||||||
|
id="path27"
|
||||||
|
inkscape:connector-curvature="0"
|
||||||
|
style="fill:#FFFFFF" />
|
||||||
|
</g>
|
||||||
|
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -2,6 +2,20 @@
|
||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
"@azure/msal-browser@^2.14.2":
|
||||||
|
version "2.14.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@azure/msal-browser/-/msal-browser-2.14.2.tgz#4efa031ad16d5a3a527eddb6222e15dd6e2ea3e8"
|
||||||
|
integrity sha512-JKHE9Rer41CI8tweiyE91M8ZbGvQV9P+jOPB4ZtPxyxCi2f7ED3jNfdzyUJ1eGB+hCRnvO56M1Xc61T1R+JfYg==
|
||||||
|
dependencies:
|
||||||
|
"@azure/msal-common" "^4.3.0"
|
||||||
|
|
||||||
|
"@azure/msal-common@^4.3.0":
|
||||||
|
version "4.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@azure/msal-common/-/msal-common-4.3.0.tgz#b540e92748656724088bf77192e59943a93135bc"
|
||||||
|
integrity sha512-jFqUWe83wVb6O8cNGGBFg2QlKvqM1ezUgJTEV7kIsAPX0RXhGFE4B1DLNt6hCnkTXDbw+KGW0zgxOEr4MJQwLw==
|
||||||
|
dependencies:
|
||||||
|
debug "^4.1.1"
|
||||||
|
|
||||||
"@babel/code-frame@7.10.4":
|
"@babel/code-frame@7.10.4":
|
||||||
version "7.10.4"
|
version "7.10.4"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
|
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue