diff --git a/packages/app/package.json b/packages/app/package.json
index 25dcbbd945..492ca8e900 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -29,6 +29,7 @@
"@backstage/plugin-code-coverage": "^0.1.27",
"@backstage/plugin-cost-insights": "^0.11.22",
"@backstage/plugin-explore": "^0.3.31",
+ "@backstage/plugin-gcalendar-homepage": "^0.0.0",
"@backstage/plugin-gcp-projects": "^0.3.19",
"@backstage/plugin-github-actions": "^0.5.0",
"@backstage/plugin-gocd": "^0.1.6",
@@ -59,10 +60,10 @@
"@material-ui/icons": "^4.9.1",
"@material-ui/lab": "4.0.0-alpha.57",
"@octokit/rest": "^18.5.3",
+ "@roadiehq/backstage-plugin-buildkite": "^1.3.8",
"@roadiehq/backstage-plugin-github-insights": "^1.5.0",
"@roadiehq/backstage-plugin-github-pull-requests": "^1.4.0",
"@roadiehq/backstage-plugin-travis-ci": "^1.3.6",
- "@roadiehq/backstage-plugin-buildkite": "^1.3.8",
"history": "^5.0.0",
"prop-types": "^15.7.2",
"react": "^17.0.2",
diff --git a/packages/app/src/apis.ts b/packages/app/src/apis.ts
index d587a88402..c0d6b0cea5 100644
--- a/packages/app/src/apis.ts
+++ b/packages/app/src/apis.ts
@@ -33,7 +33,12 @@ import {
createApiFactory,
errorApiRef,
githubAuthApiRef,
+ googleAuthApiRef,
} from '@backstage/core-plugin-api';
+import {
+ GCalendarApiClient,
+ gcalendarApiRef,
+} from '@backstage/plugin-gcalendar-homepage';
export const apis: AnyApiFactory[] = [
createApiFactory({
@@ -41,9 +46,14 @@ export const apis: AnyApiFactory[] = [
deps: { configApi: configApiRef },
factory: ({ configApi }) => ScmIntegrationsApi.fromConfig(configApi),
}),
-
ScmAuth.createDefaultApiFactory(),
+ createApiFactory({
+ api: gcalendarApiRef,
+ deps: { authApi: googleAuthApiRef },
+ factory: deps => new GCalendarApiClient(deps),
+ }),
+
createApiFactory({
api: graphQlBrowseApiRef,
deps: { errorApi: errorApiRef, githubAuthApi: githubAuthApiRef },
diff --git a/packages/app/src/components/home/HomePage.tsx b/packages/app/src/components/home/HomePage.tsx
index 5a19cef6f6..4e83920121 100644
--- a/packages/app/src/components/home/HomePage.tsx
+++ b/packages/app/src/components/home/HomePage.tsx
@@ -25,6 +25,7 @@ import {
} from '@backstage/plugin-home';
import { Content, Header, Page } from '@backstage/core-components';
import { HomePageSearchBar } from '@backstage/plugin-search';
+import { CalendarCard } from '@backstage/plugin-gcalendar-homepage';
import Grid from '@material-ui/core/Grid';
import React from 'react';
@@ -100,6 +101,9 @@ export const HomePage = () => (
]}
/>
+
+
+
diff --git a/plugins/gcalendar-homepage/.eslintrc.js b/plugins/gcalendar-homepage/.eslintrc.js
new file mode 100644
index 0000000000..13573efa9c
--- /dev/null
+++ b/plugins/gcalendar-homepage/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: [require.resolve('@backstage/cli/config/eslint')],
+};
diff --git a/plugins/gcalendar-homepage/README.md b/plugins/gcalendar-homepage/README.md
new file mode 100644
index 0000000000..6a7bfd9e33
--- /dev/null
+++ b/plugins/gcalendar-homepage/README.md
@@ -0,0 +1,13 @@
+# gcalendar-homepage
+
+Welcome to the gcalendar-homepage plugin!
+
+_This plugin was created through the Backstage CLI_
+
+## Getting started
+
+Your plugin has been added to the example app in this repository, meaning you'll be able to access it by running `yarn start` in the root directory, and then navigating to [/gcalendar-homepage](http://localhost:3000/gcalendar-homepage).
+
+You can also serve the plugin in isolation by running `yarn start` in the plugin directory.
+This method of serving the plugin provides quicker iteration speed and a faster startup and hot reloads.
+It is only meant for local development, and the setup for it can be found inside the [/dev](./dev) directory.
diff --git a/plugins/gcalendar-homepage/dev/index.tsx b/plugins/gcalendar-homepage/dev/index.tsx
new file mode 100644
index 0000000000..1fd27d507d
--- /dev/null
+++ b/plugins/gcalendar-homepage/dev/index.tsx
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { createDevApp } from '@backstage/dev-utils';
+import { gcalendarHomepagePlugin } from '../src/plugin';
+
+createDevApp().registerPlugin(gcalendarHomepagePlugin).render();
diff --git a/plugins/gcalendar-homepage/package.json b/plugins/gcalendar-homepage/package.json
new file mode 100644
index 0000000000..871c714823
--- /dev/null
+++ b/plugins/gcalendar-homepage/package.json
@@ -0,0 +1,64 @@
+{
+ "name": "@backstage/plugin-gcalendar-homepage",
+ "version": "0.0.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.esm.js",
+ "types": "dist/index.d.ts"
+ },
+ "scripts": {
+ "build": "backstage-cli plugin:build",
+ "start": "backstage-cli plugin:serve",
+ "lint": "backstage-cli lint",
+ "test": "backstage-cli test",
+ "diff": "backstage-cli plugin:diff",
+ "prepack": "backstage-cli prepack",
+ "postpack": "backstage-cli postpack",
+ "clean": "backstage-cli clean"
+ },
+ "dependencies": {
+ "@backstage/core-components": "^0.8.9",
+ "@backstage/core-plugin-api": "^0.6.1",
+ "@backstage/dev-utils": "^0.2.22",
+ "@backstage/theme": "^0.2.15",
+ "@material-ui/core": "^4.9.13",
+ "@material-ui/icons": "^4.9.1",
+ "@material-ui/lab": "4.0.0-alpha.57",
+ "@testing-library/jest-dom": "^5.16.2",
+ "axios": "^0.26.0",
+ "classnames": "^2.3.1",
+ "cross-fetch": "^3.1.5",
+ "lodash": "^4.17.21",
+ "luxon": "^2.3.0",
+ "material-ui-popup-state": "^2.0.0",
+ "react-query": "^3.34.16",
+ "react-use": "^17.2.4",
+ "sanitize-html": "^2.7.0"
+ },
+ "peerDependencies": {
+ "react": "^16.13.1 || ^17.0.0"
+ },
+ "devDependencies": {
+ "@backstage/cli": "^0.14.0",
+ "@backstage/core-app-api": "^0.5.3",
+ "@backstage/dev-utils": "^0.2.22",
+ "@backstage/test-utils": "^0.2.5",
+ "@testing-library/jest-dom": "^5.10.1",
+ "@testing-library/react": "^11.2.5",
+ "@testing-library/user-event": "^13.1.8",
+ "@types/gapi": "^0.0.41",
+ "@types/gapi.auth2": "^0.0.56",
+ "@types/gapi.client.calendar": "^3.0.10",
+ "@types/jest": "*",
+ "@types/node": "*",
+ "@types/sanitize-html": "^2.6.2",
+ "cross-fetch": "^3.1.5",
+ "msw": "^0.35.0"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/plugins/gcalendar-homepage/src/api/index.ts b/plugins/gcalendar-homepage/src/api/index.ts
new file mode 100644
index 0000000000..58fae449c3
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/api/index.ts
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import axios, { AxiosInstance } from 'axios';
+
+import { OAuthApi, createApiRef } from '@backstage/core-plugin-api';
+
+import { GCalendar, GCalendarEvent } from '../components/CalendarCard/types';
+
+type Options = {
+ authApi: OAuthApi;
+};
+
+export const gcalendarApiRef = createApiRef({
+ id: 'plugin.gcalendar.service',
+});
+
+export class GCalendarApiClient {
+ private readonly authApi: OAuthApi;
+ private readonly http: AxiosInstance;
+
+ constructor(options: Options) {
+ this.authApi = options.authApi;
+ this.http = axios.create({
+ baseURL: 'https://www.googleapis.com/calendar/v3',
+ });
+ this.http.interceptors.request.use(async config => {
+ const token = await this.authApi.getAccessToken();
+ if (!config.headers) {
+ config.headers = {};
+ }
+ config.headers.Authorization = `Bearer ${token}`;
+
+ return config;
+ });
+ }
+
+ public async getCalendars(params?: any): Promise<{ items: GCalendar[] }> {
+ const { data } = await this.http.get('/users/me/calendarList', {
+ params,
+ });
+
+ return data;
+ }
+
+ public async getEvents(
+ calendarId: string,
+ params?: any,
+ ): Promise<{ items: GCalendarEvent[] }> {
+ const { data } = await this.http.get(
+ `/calendars/${encodeURIComponent(calendarId)}/events`,
+ {
+ params,
+ },
+ );
+
+ return data;
+ }
+}
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/AttendeeChip.test.tsx b/plugins/gcalendar-homepage/src/components/CalendarCard/AttendeeChip.test.tsx
new file mode 100644
index 0000000000..da9c587f23
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/AttendeeChip.test.tsx
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React from 'react';
+
+import { renderInTestApp } from '@backstage/test-utils';
+
+import { AttendeeChip } from './AttendeeChip';
+import { EventAttendee, ResponseStatus } from './types';
+
+describe('', () => {
+ it('renders attendee email', async () => {
+ const email = 'test@test.com';
+ const user: EventAttendee = {
+ email,
+ responseStatus: ResponseStatus.needsAction,
+ };
+ const { queryByText } = await renderInTestApp();
+ expect(queryByText(email)).toBeInTheDocument();
+ });
+
+ it('renders accepted icon', async () => {
+ const email = 'test@test.com';
+ const user: EventAttendee = {
+ email,
+ responseStatus: ResponseStatus.accepted,
+ };
+ const { getByTestId } = await renderInTestApp();
+ expect(getByTestId('accepted-icon')).toBeInTheDocument();
+ });
+
+ it('renders declined icon', async () => {
+ const email = 'test@test.com';
+ const user: EventAttendee = {
+ email,
+ responseStatus: ResponseStatus.declined,
+ };
+ const { getByTestId } = await renderInTestApp();
+ expect(getByTestId('declined-icon')).toBeInTheDocument();
+ });
+});
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/AttendeeChip.tsx b/plugins/gcalendar-homepage/src/components/CalendarCard/AttendeeChip.tsx
new file mode 100644
index 0000000000..d06e69aa49
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/AttendeeChip.tsx
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React from 'react';
+
+import { BackstageTheme } from '@backstage/theme';
+
+import { Badge, Chip, makeStyles } from '@material-ui/core';
+import CancelIcon from '@material-ui/icons/Cancel';
+import CheckIcon from '@material-ui/icons/CheckCircle';
+
+import { EventAttendee, ResponseStatus } from './types';
+
+const useStyles = makeStyles((theme: BackstageTheme) => {
+ const getIconColor = (responseStatus?: string) => {
+ if (!responseStatus) return theme.palette.primary.light;
+
+ return {
+ [ResponseStatus.accepted]: theme.palette.status.ok,
+ [ResponseStatus.declined]: theme.palette.status.error,
+ }[responseStatus];
+ };
+
+ return {
+ responseStatus: {
+ color: ({ responseStatus }: { responseStatus?: string }) =>
+ getIconColor(responseStatus),
+ },
+ badge: {
+ right: 10,
+ top: 5,
+ '& svg': {
+ height: 16,
+ width: 16,
+ background: '#fff',
+ },
+ },
+ };
+});
+
+const ResponseIcon = ({ responseStatus }: any) => {
+ if (responseStatus === ResponseStatus.accepted) {
+ return ;
+ }
+ if (responseStatus === ResponseStatus.declined) {
+ return ;
+ }
+
+ return null;
+};
+
+type AttendeeChipProps = {
+ user: EventAttendee;
+};
+
+export const AttendeeChip = ({ user }: AttendeeChipProps) => {
+ const classes = useStyles({ responseStatus: user.responseStatus });
+
+ return (
+ }
+ >
+
+
+ );
+};
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarCard.test.tsx b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarCard.test.tsx
new file mode 100644
index 0000000000..1b53d329d5
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarCard.test.tsx
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React from 'react';
+
+import { googleAuthApiRef, storageApiRef } from '@backstage/core-plugin-api';
+import {
+ MockStorageApi,
+ TestApiProvider,
+ renderInTestApp,
+} from '@backstage/test-utils';
+
+import { CalendarCardContainer } from '.';
+import { gcalendarApiRef, gcalendarHomepagePlugin } from '../..';
+
+describe('', () => {
+ const primaryCalendar = {
+ id: 'test-1@test.com',
+ summary: 'test-1@test.com',
+ primary: true,
+ };
+ const nonPrimaryCalendar = {
+ id: 'test-2@test.com',
+ summary: 'test-2@test.com',
+ primary: false,
+ };
+ const calendarData = {
+ items: [primaryCalendar, nonPrimaryCalendar],
+ };
+ const eventData = {
+ items: [
+ {
+ id: '1',
+ summary: 'Test event',
+ },
+ {
+ id: '2',
+ summary: 'Test event',
+ },
+ ],
+ };
+
+ const getMockedApi = (calendarMock = {}, eventMock = {}) => ({
+ getCalendars: jest.fn().mockResolvedValue(calendarMock),
+ getEvents: jest.fn().mockResolvedValue(eventMock),
+ });
+
+ const getAuthMockApi = (token = 'token') => ({
+ getAccessToken: jest.fn().mockResolvedValue(token),
+ });
+
+ const mockStorage = MockStorageApi.create();
+
+ it('should render "Sign in" button if user not signed in', async () => {
+ const rendered = await renderInTestApp(
+
+
+ ,
+ );
+
+ expect(rendered.queryByText('Sign in')).toBeInTheDocument();
+ });
+
+ it('should render empty card', async () => {
+ const rendered = await renderInTestApp(
+
+
+ ,
+ );
+
+ expect(rendered.queryByText('No events')).toBeInTheDocument();
+ expect(rendered.queryByText('Go to Calendar')).toBeInTheDocument();
+ });
+
+ it('should select primary calendar by default', async () => {
+ const rendered = await renderInTestApp(
+
+
+ ,
+ );
+
+ expect(rendered.queryByText(primaryCalendar.summary)).toBeInTheDocument();
+ expect(
+ rendered.queryByText(nonPrimaryCalendar.summary),
+ ).not.toBeInTheDocument();
+ });
+
+ it('should render calendar events', async () => {
+ const rendered = await renderInTestApp(
+
+
+ ,
+ );
+
+ expect(rendered.queryByText('No events')).not.toBeInTheDocument();
+ expect(rendered.queryAllByText('Test event')).toHaveLength(2);
+ });
+
+ it('should select stored calendar', async () => {
+ mockStorage
+ .forBucket(gcalendarHomepagePlugin.getId())
+ .set('google_calendars_selected', [nonPrimaryCalendar.id]);
+
+ const rendered = await renderInTestApp(
+
+
+ ,
+ );
+
+ expect(
+ rendered.queryByText(nonPrimaryCalendar.summary),
+ ).toBeInTheDocument();
+ expect(
+ rendered.queryByText(primaryCalendar.summary),
+ ).not.toBeInTheDocument();
+ });
+});
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarCard.tsx b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarCard.tsx
new file mode 100644
index 0000000000..8111e68f84
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarCard.tsx
@@ -0,0 +1,146 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { sortBy } from 'lodash';
+import { DateTime } from 'luxon';
+import React, { useEffect, useMemo, useState } from 'react';
+
+import { InfoCard, Progress } from '@backstage/core-components';
+import { useAnalytics } from '@backstage/core-plugin-api';
+
+import { Box, IconButton, Typography } from '@material-ui/core';
+import PrevIcon from '@material-ui/icons/NavigateBefore';
+import NextIcon from '@material-ui/icons/NavigateNext';
+
+import {
+ useCalendarsQuery,
+ useEventsQuery,
+ useSignIn,
+ useStoredCalendars,
+} from '../../hooks';
+import calendarCardIcon from '../../icons/calendarIcon.svg';
+import { CalendarEvent } from './CalendarEvent';
+import { CalendarSelect } from './CalendarSelect';
+import { SignInContent } from './SignInContent';
+import { getStartDate } from './util';
+
+export const CalendarCard = () => {
+ const [date, setDate] = useState(DateTime.now());
+ const analytics = useAnalytics();
+
+ const changeDay = (offset = 1) => {
+ setDate(prev => prev.plus({ day: offset }));
+ analytics.captureEvent('click', 'change date');
+ };
+
+ const { isSignedIn, isInitialized, signIn } = useSignIn();
+
+ useEffect(() => {
+ signIn(true);
+ }, [signIn]);
+
+ const { isLoading: isCalendarLoading, data } = useCalendarsQuery({
+ enabled: isSignedIn,
+ });
+ const calendars = useMemo(() => data?.items || [], [data]);
+ const primaryCalendarId = calendars.find(c => c.primary === true)?.id;
+ const defaultSelectedCalendars = primaryCalendarId ? [primaryCalendarId] : [];
+ const [storedCalendars, setStoredCalendars] = useStoredCalendars(
+ defaultSelectedCalendars,
+ );
+
+ const { events, isLoading: isEventLoading } = useEventsQuery({
+ calendars,
+ selectedCalendars: storedCalendars,
+ enabled: isSignedIn && calendars.length > 0,
+ timeMin: date.startOf('day').toISO(),
+ timeMax: date.endOf('day').toISO(),
+ timeZone: date.zoneName,
+ });
+
+ return (
+
+
+
+
+ {isSignedIn ? (
+ <>
+ changeDay(-1)} size="small">
+
+
+ changeDay(1)} size="small">
+
+
+
+
+ {date.toLocaleString({
+ weekday: 'short',
+ month: 'short',
+ day: 'numeric',
+ })}
+
+
+
+
+
+ >
+ ) : (
+ Agenda
+ )}
+
+ }
+ deepLink={{
+ link: 'https://calendar.google.com/',
+ title: 'Go to Calendar',
+ }}
+ >
+
+ {(isCalendarLoading || !isInitialized || isEventLoading) && (
+
+
+
+ )}
+ {!isSignedIn && isInitialized && (
+ signIn(false)} />
+ )}
+ {!isEventLoading && !isCalendarLoading && isSignedIn && (
+
+ {events.length === 0 && (
+
+
+ No events
+
+
+ )}
+ {sortBy(events, [getStartDate]).map(event => (
+
+ ))}
+
+ )}
+
+
+ );
+};
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarCardContainer.tsx b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarCardContainer.tsx
new file mode 100644
index 0000000000..b1595308ad
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarCardContainer.tsx
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React from 'react';
+import { QueryClient, QueryClientProvider } from 'react-query';
+
+import { CalendarCard } from './CalendarCard';
+
+const queryClient = new QueryClient();
+
+export const CalendarCardContainer = () => {
+ return (
+
+
+
+ );
+};
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarEvent.test.tsx b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarEvent.test.tsx
new file mode 100644
index 0000000000..7a48903bec
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarEvent.test.tsx
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { fireEvent } from '@testing-library/react';
+import React from 'react';
+
+import { renderInTestApp } from '@backstage/test-utils';
+
+import { CalendarEvent } from './CalendarEvent';
+
+describe('', () => {
+ const event = {
+ summary: 'Test event',
+ htmlLink: '/calendar-link',
+ start: {
+ dateTime: '2022-02-09T10:15:00',
+ },
+ end: {
+ dateTime: '2022-02-09T10:45:00',
+ },
+ conferenceData: {
+ entryPoints: [{ entryPointType: 'video', uri: '/zoom-link' }],
+ },
+ description: 'Test description',
+ attendees: [
+ {
+ email: 'test@test.com',
+ },
+ ],
+ };
+
+ it('should render calendar event', async () => {
+ const { queryByText, queryByTestId } = await renderInTestApp(
+ ,
+ );
+ expect(queryByText(event.summary)).toBeInTheDocument();
+ expect(queryByTestId('calendar-event-zoom-link')).toBeInTheDocument();
+ expect(queryByTestId('calendar-event-zoom-link')).toHaveAttribute(
+ 'href',
+ event.conferenceData.entryPoints[0].uri,
+ );
+ expect(queryByTestId('calendar-event-time')).toBeInTheDocument();
+ });
+
+ it('should not render time for events longer than 1 day', async () => {
+ const allDayEvent = {
+ summary: 'Test event',
+ start: {
+ date: '2022-02-09',
+ },
+ end: {
+ date: '2022-02-19',
+ },
+ };
+ const { queryByText, queryByTestId } = await renderInTestApp(
+ ,
+ );
+ expect(queryByText(allDayEvent.summary)).toBeInTheDocument();
+ expect(queryByTestId('calendar-event-time')).not.toBeInTheDocument();
+ });
+
+ it('should show popover on click', async () => {
+ const { queryByTestId, getByTestId } = await renderInTestApp(
+ ,
+ );
+ expect(queryByTestId('calendar-event-popover')).not.toBeInTheDocument();
+
+ fireEvent.click(getByTestId('calendar-event'));
+ expect(queryByTestId('calendar-event-popover')).toBeInTheDocument();
+ });
+});
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarEvent.tsx b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarEvent.tsx
new file mode 100644
index 0000000000..4d448c9cd2
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarEvent.tsx
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import classnames from 'classnames';
+import {
+ bindPopover,
+ bindTrigger,
+ usePopupState,
+} from 'material-ui-popup-state/hooks';
+import React, { useState } from 'react';
+
+import { useAnalytics } from '@backstage/core-plugin-api';
+
+import {
+ Box,
+ Link,
+ Paper,
+ Popover,
+ Tooltip,
+ Typography,
+ makeStyles,
+} from '@material-ui/core';
+
+import zoomIcon from '../../icons/zoomIcon.svg';
+import { CalendarEventPopoverContent } from './CalendarEventPopoverContent';
+import { GCalendarEvent, ResponseStatus } from './types';
+import { getTimePeriod, getZoomLink, isAllDay, isPassed } from './util';
+
+const useStyles = makeStyles(theme => ({
+ event: {
+ display: 'flex',
+ alignItems: 'center',
+ marginBottom: theme.spacing(1),
+ cursor: 'pointer',
+ paddingRight: 12,
+ },
+ declined: {
+ textDecoration: 'line-through',
+ },
+ passed: {
+ opacity: 0.6,
+ transition: 'opacity 0.15s ease-in-out',
+ '&:hover': {
+ opacity: 1,
+ },
+ },
+ link: {
+ width: 48,
+ height: 48,
+ display: 'inline-block',
+ padding: 8,
+ borderRadius: '50%',
+ '&:hover': {
+ backgroundColor: theme.palette.grey[100],
+ },
+ },
+ calendarColor: ({ event }: any) => ({
+ width: 8,
+ borderTopLeftRadius: 4,
+ borderBottomLeftRadius: 4,
+ backgroundColor: event.primary
+ ? theme.palette.primary.light
+ : event.backgroundColor,
+ }),
+}));
+
+export const CalendarEvent = ({ event }: { event: GCalendarEvent }) => {
+ const classes = useStyles({ event });
+ const popoverState = usePopupState({
+ variant: 'popover',
+ popupId: event.id,
+ disableAutoFocus: true,
+ });
+ const [hovered, setHovered] = useState(false);
+ const analytics = useAnalytics();
+ const zoomLink = getZoomLink(event);
+
+ const { onClick, ...restBindProps } = bindTrigger(popoverState);
+
+ return (
+ <>
+ {
+ onClick(e);
+ analytics.captureEvent('click', 'event info');
+ }}
+ {...restBindProps}
+ onMouseEnter={() => setHovered(true)}
+ onMouseLeave={() => setHovered(false)}
+ elevation={hovered ? 4 : 1}
+ className={classnames(classes.event, {
+ [classes.passed]: isPassed(event),
+ })}
+ data-testid="calendar-event"
+ >
+
+
+
+ {event.summary}
+
+ {!isAllDay(event) && (
+
+ {getTimePeriod(event)}
+
+ )}
+
+
+ {zoomLink && (
+
+ {
+ e.stopPropagation();
+ analytics.captureEvent('click', 'zoom link');
+ }}
+ >
+
+
+
+ )}
+
+
+
+
+
+ >
+ );
+};
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarEventPopoverContent.test.tsx b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarEventPopoverContent.test.tsx
new file mode 100644
index 0000000000..fe48b2fd5a
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarEventPopoverContent.test.tsx
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React from 'react';
+
+import { renderInTestApp } from '@backstage/test-utils';
+
+import { CalendarEventPopoverContent } from './CalendarEventPopoverContent';
+
+describe('', () => {
+ const event = {
+ summary: 'Test event',
+ htmlLink: '/calendar-link',
+ conferenceData: {
+ entryPoints: [{ entryPointType: 'video', uri: '/zoom-link' }],
+ },
+ description: 'Test description',
+ attendees: [
+ {
+ email: 'test@test.com',
+ },
+ ],
+ };
+
+ it('should render event info', async () => {
+ const { queryByText, queryByTestId } = await renderInTestApp(
+ ,
+ );
+ expect(queryByText(event.summary)).toBeInTheDocument();
+ expect(queryByText(event.description)).toBeInTheDocument();
+ expect(queryByText(event.attendees[0].email)).toBeInTheDocument();
+ expect(queryByText('Join Zoom Meeting')).toBeInTheDocument();
+ expect(queryByText('Join Zoom Meeting')?.closest('a')).toHaveAttribute(
+ 'href',
+ event.conferenceData.entryPoints[0].uri,
+ );
+ expect(queryByTestId('open-calendar-link')).toHaveAttribute(
+ 'href',
+ event.htmlLink,
+ );
+ expect(queryByText(event.attendees[0].email)).toBeInTheDocument();
+ });
+});
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarEventPopoverContent.tsx b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarEventPopoverContent.tsx
new file mode 100644
index 0000000000..749c3cd8ba
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarEventPopoverContent.tsx
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { sortBy } from 'lodash';
+import React from 'react';
+import sanitizeHtml from 'sanitize-html';
+
+import { useAnalytics } from '@backstage/core-plugin-api';
+
+import {
+ Box,
+ Divider,
+ IconButton,
+ Link,
+ Tooltip,
+ Typography,
+ makeStyles,
+} from '@material-ui/core';
+import ArrowForwardIcon from '@material-ui/icons/ArrowForward';
+
+import { AttendeeChip } from './AttendeeChip';
+import { GCalendarEvent } from './types';
+import { getTimePeriod, getZoomLink } from './util';
+
+const useStyles = makeStyles(theme => {
+ return {
+ description: {
+ wordBreak: 'break-word',
+ '& a': {
+ color: theme.palette.primary.main,
+ fontWeight: 500,
+ },
+ },
+ divider: {
+ marginTop: theme.spacing(2),
+ marginBottom: theme.spacing(2),
+ },
+ };
+});
+
+type CalendarEventPopoverProps = {
+ event: GCalendarEvent;
+};
+
+export const CalendarEventPopoverContent = ({
+ event,
+}: CalendarEventPopoverProps) => {
+ const classes = useStyles({ event });
+ const analytics = useAnalytics();
+ const zoomLink = getZoomLink(event);
+
+ return (
+
+
+
+ {event.summary}
+ {getTimePeriod(event)}
+
+ {event.htmlLink && (
+
+
+ analytics.captureEvent('click', 'open in calendar')
+ }
+ >
+
+
+
+
+
+ )}
+
+ {zoomLink && (
+ analytics.captureEvent('click', 'zoom link')}
+ >
+ Join Zoom Meeting
+
+ )}
+
+ {event.description && (
+ <>
+
+
+ >
+ )}
+
+ {event.attendees && (
+ <>
+
+
+ Attendees
+
+ {sortBy(event.attendees || [], 'email').map(user => (
+
+ ))}
+
+ >
+ )}
+
+ );
+};
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarSelect.tsx b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarSelect.tsx
new file mode 100644
index 0000000000..71d25329b5
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/CalendarSelect.tsx
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { sortBy } from 'lodash';
+import React from 'react';
+
+import {
+ Checkbox,
+ FormControl,
+ Input,
+ ListItemText,
+ MenuItem,
+ Select,
+ Typography,
+ makeStyles,
+} from '@material-ui/core';
+
+import { GCalendar } from './types';
+
+const useStyles = makeStyles({
+ formControl: {
+ width: 120,
+ },
+ selectedCalendars: {
+ textOverflow: 'ellipsis',
+ overflow: 'hidden',
+ },
+});
+
+type CalendarSelectProps = {
+ disabled: boolean;
+ selectedCalendars?: string[];
+ setSelectedCalendars: (value: string[]) => void;
+ calendars: GCalendar[];
+};
+
+export const CalendarSelect = ({
+ disabled,
+ selectedCalendars = [],
+ setSelectedCalendars,
+ calendars,
+}: CalendarSelectProps) => {
+ const classes = useStyles();
+
+ return (
+
+
+
+ );
+};
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/SignInContent.tsx b/plugins/gcalendar-homepage/src/components/CalendarCard/SignInContent.tsx
new file mode 100644
index 0000000000..c748fecea0
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/SignInContent.tsx
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import React from 'react';
+
+import { Box, Button, styled } from '@material-ui/core';
+
+import { CalendarEvent } from './CalendarEvent';
+import { events } from './signInEventMock';
+
+type SignInContentProps = {
+ handleAuthClick: React.MouseEventHandler;
+};
+
+const TransparentBox = styled(Box)({
+ opacity: 0.3,
+ filter: 'blur(1.5px)',
+});
+
+export const SignInContent = ({ handleAuthClick }: SignInContentProps) => {
+ return (
+
+
+ {events.map(event => (
+
+ ))}
+
+
+
+
+
+
+ );
+};
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/index.ts b/plugins/gcalendar-homepage/src/components/CalendarCard/index.ts
new file mode 100644
index 0000000000..c6f80f89ee
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/index.ts
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export { CalendarCardContainer } from './CalendarCardContainer';
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/signInEventMock.ts b/plugins/gcalendar-homepage/src/components/CalendarCard/signInEventMock.ts
new file mode 100644
index 0000000000..8d76757a59
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/signInEventMock.ts
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { GCalendarEvent } from './types';
+
+export const events: GCalendarEvent[] = [
+ {
+ id: '1',
+ htmlLink: 'https://www.google.com/calendar/',
+ summary: 'Backstage Community Sessions',
+ start: {
+ dateTime: '2021-12-07T09:00:00+01:00',
+ timeZone: 'Europe/London',
+ },
+ end: {
+ dateTime: '2021-12-07T10:00:00+01:00',
+ timeZone: 'Europe/London',
+ },
+ },
+ {
+ id: '2',
+ htmlLink: 'https://www.google.com/calendar/',
+ summary: 'Backstage Community Sessions',
+ start: {
+ dateTime: '2021-12-07T10:30:00+01:00',
+ timeZone: 'Europe/London',
+ },
+ end: {
+ dateTime: '2021-12-07T10:45:00+01:00',
+ timeZone: 'Europe/London',
+ },
+ conferenceData: {
+ entryPoints: [
+ {
+ entryPointType: 'video',
+ uri: 'https://zoom.us',
+ },
+ ],
+ },
+ },
+ {
+ id: '3',
+ htmlLink: 'https://www.google.com/calendar',
+ summary: 'Backstage Community Sessions',
+ start: {
+ dateTime: '2021-12-07T12:00:00+01:00',
+ timeZone: 'Europe/London',
+ },
+ end: {
+ dateTime: '2021-12-07T13:00:00+01:00',
+ timeZone: 'Europe/London',
+ },
+ conferenceData: {
+ entryPoints: [
+ {
+ entryPointType: 'video',
+ uri: 'https://zoom.us',
+ label: 'zoom.us',
+ },
+ ],
+ },
+ },
+ {
+ id: '4',
+ htmlLink: 'https://www.google.com/calendar',
+ summary: 'Backstage Community Sessions',
+ start: {
+ dateTime: '2021-12-07T15:00:00+01:00',
+ timeZone: 'Europe/London',
+ },
+ end: {
+ dateTime: '2021-12-07T16:30:00+01:00',
+ timeZone: 'Europe/London',
+ },
+ conferenceData: {
+ entryPoints: [
+ {
+ entryPointType: 'video',
+ uri: 'https://zoom.us',
+ },
+ ],
+ },
+ },
+ {
+ id: '5',
+ htmlLink: 'https://www.google.com/calendar',
+ summary: 'Backstage Community Sessions',
+ start: {
+ dateTime: '2021-12-07T17:00:00+01:00',
+ timeZone: 'Europe/London',
+ },
+ end: {
+ dateTime: '2021-12-07T17:30:00+01:00',
+ timeZone: 'Europe/London',
+ },
+ conferenceData: {
+ entryPoints: [
+ {
+ entryPointType: 'video',
+ uri: 'https://zoom.us',
+ },
+ ],
+ },
+ },
+];
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/types.ts b/plugins/gcalendar-homepage/src/components/CalendarCard/types.ts
new file mode 100644
index 0000000000..1423b91aca
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/types.ts
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+///
+///
+
+export type GCalendar = gapi.client.calendar.CalendarListEntry;
+
+export type EventAttendee = gapi.client.calendar.EventAttendee;
+
+export type GCalendarEvent = gapi.client.calendar.Event &
+ Pick &
+ Pick & {
+ calendarId?: string;
+ };
+
+export enum ResponseStatus {
+ needsAction = 'needsAction',
+ accepted = 'accepted',
+ declined = 'declined',
+ maybe = 'tentative',
+}
diff --git a/plugins/gcalendar-homepage/src/components/CalendarCard/util.ts b/plugins/gcalendar-homepage/src/components/CalendarCard/util.ts
new file mode 100644
index 0000000000..e0c2abf259
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/components/CalendarCard/util.ts
@@ -0,0 +1,82 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { DateTime } from 'luxon';
+
+import { GCalendarEvent } from './types';
+
+export function getZoomLink(event: GCalendarEvent) {
+ const videoEntrypoint = event.conferenceData?.entryPoints?.find(
+ e => e.entryPointType === 'video',
+ );
+
+ return videoEntrypoint?.uri ?? '';
+}
+
+export function getTimePeriod(event: GCalendarEvent) {
+ if (isAllDay(event)) {
+ return getAllDayTimePeriod(event);
+ }
+
+ const format: Intl.DateTimeFormatOptions = {
+ hour: '2-digit',
+ minute: '2-digit',
+ };
+
+ const startTime = DateTime.fromISO(event.start?.dateTime || '');
+ const endTime = DateTime.fromISO(event.end?.dateTime || '');
+
+ return `${startTime.toLocaleString(format)} - ${endTime.toLocaleString(
+ format,
+ )}`;
+}
+
+function getAllDayTimePeriod(event: GCalendarEvent) {
+ const format: Intl.DateTimeFormatOptions = { month: 'long', day: 'numeric' };
+ const startTime = DateTime.fromISO(
+ event.start?.dateTime || event.start?.date || '',
+ );
+ const endTime = DateTime.fromISO(
+ event.end?.dateTime || event.end?.date || '',
+ ).minus({ day: 1 });
+
+ if (startTime.toISO() === endTime.toISO()) {
+ return startTime.toLocaleString(format);
+ }
+
+ return `${startTime.toLocaleString(format)} - ${endTime.toLocaleString(
+ format,
+ )}`;
+}
+
+export function isPassed(event: GCalendarEvent) {
+ if (!event.end?.dateTime && !event.end?.date) return false;
+ const eventDate = DateTime.fromISO(event.end?.dateTime || event.end?.date!);
+ return DateTime.now() >= eventDate;
+}
+
+export function isAllDay(event: GCalendarEvent) {
+ if (event.start?.date || event.end?.date) {
+ return true;
+ }
+ const startTime = DateTime.fromISO(event.start?.dateTime || '');
+ const endTime = DateTime.fromISO(event.end?.dateTime || '');
+
+ return endTime.diff(startTime, 'day').days >= 1;
+}
+
+export function getStartDate(event: GCalendarEvent) {
+ return DateTime.fromISO(event.start?.dateTime || event.start?.date || '');
+}
diff --git a/plugins/gcalendar-homepage/src/hooks/index.ts b/plugins/gcalendar-homepage/src/hooks/index.ts
new file mode 100644
index 0000000000..ef922cc4a3
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/hooks/index.ts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export { useCalendarsQuery } from './useCalendarsQuery';
+export { useEventsQuery } from './useEventsQuery';
+export { useSignIn } from './useSignIn';
+export { useStoredCalendars } from './useStoredCalendars';
diff --git a/plugins/gcalendar-homepage/src/hooks/useCalendarsQuery.ts b/plugins/gcalendar-homepage/src/hooks/useCalendarsQuery.ts
new file mode 100644
index 0000000000..ed31e23c79
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/hooks/useCalendarsQuery.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { useQuery } from 'react-query';
+
+import { errorApiRef, useApi } from '@backstage/core-plugin-api';
+
+import { gcalendarApiRef } from '../api';
+
+type Options = {
+ enabled: boolean;
+ refreshTime?: number;
+};
+
+export const useCalendarsQuery = ({ enabled }: Options) => {
+ const calendarApi = useApi(gcalendarApiRef);
+ const errorApi = useApi(errorApiRef);
+
+ return useQuery(
+ ['calendars'],
+ async () =>
+ calendarApi.getCalendars({
+ minAccessRole: 'reader',
+ }),
+ {
+ enabled,
+ keepPreviousData: true,
+ refetchInterval: 3600000,
+ onError: () => {
+ errorApi.post({
+ name: 'API error',
+ message: 'Failed to fetch calendars.',
+ });
+ },
+ },
+ );
+};
diff --git a/plugins/gcalendar-homepage/src/hooks/useEventsQuery.ts b/plugins/gcalendar-homepage/src/hooks/useEventsQuery.ts
new file mode 100644
index 0000000000..7630c82b0d
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/hooks/useEventsQuery.ts
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { compact, unescape } from 'lodash';
+import { useMemo } from 'react';
+import { useQueries } from 'react-query';
+
+import { useApi } from '@backstage/core-plugin-api';
+
+import { gcalendarApiRef } from '../api';
+import { GCalendar, GCalendarEvent } from '../components/CalendarCard/types';
+
+type Options = {
+ selectedCalendars?: string[];
+ timeMin: string;
+ timeMax: string;
+ enabled: boolean;
+ calendars: GCalendar[];
+ timeZone: string;
+ refreshTime?: number;
+};
+
+export const useEventsQuery = ({
+ selectedCalendars = [],
+ calendars = [],
+ enabled,
+ timeMin,
+ timeMax,
+ timeZone,
+}: Options) => {
+ const calendarApi = useApi(gcalendarApiRef);
+ const eventQueries = useQueries(
+ selectedCalendars
+ .filter(id => calendars.find(c => c.id === id))
+ .map(calendarId => {
+ const calendar = calendars.find(c => c.id === calendarId);
+
+ return {
+ queryKey: ['calendarEvents', calendarId, timeMin, timeMax],
+ enabled,
+ initialData: [],
+ refetchInterval: 60000,
+ refetchIntervalInBackground: true,
+ queryFn: async (): Promise => {
+ const data = await calendarApi.getEvents(calendarId, {
+ calendarId,
+ timeMin,
+ timeMax,
+ showDeleted: false,
+ singleEvents: true,
+ maxResults: 100,
+ orderBy: 'startTime',
+ timeZone,
+ });
+
+ return (data.items || []).map(event => {
+ const responseStatus = event.attendees?.find(
+ a => !!a.self,
+ )?.responseStatus;
+
+ return {
+ ...event,
+ summary: unescape(event.summary || ''),
+ calendarId,
+ backgroundColor: calendar?.backgroundColor,
+ primary: !!calendar?.primary,
+ responseStatus,
+ };
+ });
+ },
+ };
+ }),
+ );
+
+ const events = useMemo(
+ () => compact(eventQueries.map(({ data }) => data).flat()),
+ [eventQueries],
+ );
+
+ const isLoading =
+ !!eventQueries.find(q => q.isFetching) && events.length === 0;
+
+ return { events, isLoading };
+};
diff --git a/plugins/gcalendar-homepage/src/hooks/useSignIn.ts b/plugins/gcalendar-homepage/src/hooks/useSignIn.ts
new file mode 100644
index 0000000000..351707b54e
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/hooks/useSignIn.ts
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { useCallback, useState } from 'react';
+
+import { googleAuthApiRef, useApi } from '@backstage/core-plugin-api';
+
+export const useSignIn = () => {
+ const [isSignedIn, setSignedIn] = useState(false);
+ const [isInitialized, setInitialized] = useState(false);
+ const authApi = useApi(googleAuthApiRef);
+
+ const signIn = useCallback(
+ async (optional = false) => {
+ const token = await authApi.getAccessToken(
+ 'https://www.googleapis.com/auth/calendar.readonly',
+ {
+ optional,
+ instantPopup: !optional,
+ },
+ );
+
+ setSignedIn(!!token);
+ setInitialized(true);
+ },
+ [authApi, setSignedIn],
+ );
+
+ return { isSignedIn, isInitialized, signIn };
+};
diff --git a/plugins/gcalendar-homepage/src/hooks/useStoredCalendars.ts b/plugins/gcalendar-homepage/src/hooks/useStoredCalendars.ts
new file mode 100644
index 0000000000..8c6b8c3f82
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/hooks/useStoredCalendars.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { useApi, storageApiRef } from '@backstage/core-plugin-api';
+import { useObservable } from 'react-use';
+
+import { gcalendarHomepagePlugin } from '../plugin';
+
+export enum LocalStorageKeys {
+ selectedCalendars = 'google_calendars_selected',
+}
+
+export function useStoredCalendars(
+ defaultValue: string[],
+): [string[], (value: string[]) => void] {
+ const storageBucket = gcalendarHomepagePlugin.getId();
+ const storageKey = LocalStorageKeys.selectedCalendars;
+ const storageApi = useApi(storageApiRef).forBucket(storageBucket);
+ const setValue = (value: string[]) => {
+ storageApi.set(storageKey, value);
+ };
+ const snapshot = useObservable(
+ storageApi.observe$(storageKey),
+ storageApi.snapshot(storageKey),
+ );
+ let result: string[];
+ if (snapshot.presence === 'absent') {
+ result = defaultValue;
+ } else {
+ result = snapshot.value!;
+ }
+ return [result, setValue];
+}
diff --git a/plugins/gcalendar-homepage/src/icons/calendarIcon.svg b/plugins/gcalendar-homepage/src/icons/calendarIcon.svg
new file mode 100644
index 0000000000..f5b82c824c
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/icons/calendarIcon.svg
@@ -0,0 +1,10 @@
+
\ No newline at end of file
diff --git a/plugins/gcalendar-homepage/src/icons/zoomIcon.svg b/plugins/gcalendar-homepage/src/icons/zoomIcon.svg
new file mode 100644
index 0000000000..cc6ef0316b
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/icons/zoomIcon.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/plugins/gcalendar-homepage/src/index.ts b/plugins/gcalendar-homepage/src/index.ts
new file mode 100644
index 0000000000..faaeabf754
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+export { gcalendarHomepagePlugin, CalendarCard } from './plugin';
+export * from './api';
diff --git a/plugins/gcalendar-homepage/src/plugin.test.ts b/plugins/gcalendar-homepage/src/plugin.test.ts
new file mode 100644
index 0000000000..e5604f39dc
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/plugin.test.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { gcalendarHomepagePlugin } from './plugin';
+
+describe('gcalendar-homepage', () => {
+ it('should export plugin', () => {
+ expect(gcalendarHomepagePlugin).toBeDefined();
+ });
+});
diff --git a/plugins/gcalendar-homepage/src/plugin.ts b/plugins/gcalendar-homepage/src/plugin.ts
new file mode 100644
index 0000000000..878475d1c8
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/plugin.ts
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ createApiFactory,
+ createComponentExtension,
+ createPlugin,
+ googleAuthApiRef,
+} from '@backstage/core-plugin-api';
+
+import { GCalendarApiClient, gcalendarApiRef } from './api';
+import { rootRouteRef } from './routes';
+
+export const gcalendarHomepagePlugin = createPlugin({
+ id: 'gcalendar-homepage',
+ routes: {
+ root: rootRouteRef,
+ },
+ apis: [
+ createApiFactory({
+ api: gcalendarApiRef,
+ deps: { authApi: googleAuthApiRef },
+ factory(deps) {
+ return new GCalendarApiClient(deps);
+ },
+ }),
+ ],
+});
+
+export const CalendarCard = gcalendarHomepagePlugin.provide(
+ createComponentExtension({
+ name: 'CalendarCard',
+ component: {
+ lazy: () =>
+ import('./components/CalendarCard').then(m => m.CalendarCardContainer),
+ },
+ }),
+);
diff --git a/plugins/gcalendar-homepage/src/routes.ts b/plugins/gcalendar-homepage/src/routes.ts
new file mode 100644
index 0000000000..3831bb6e37
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/routes.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import { createRouteRef } from '@backstage/core-plugin-api';
+
+export const rootRouteRef = createRouteRef({
+ id: 'gcalendar-homepage',
+});
diff --git a/plugins/gcalendar-homepage/src/setupTests.ts b/plugins/gcalendar-homepage/src/setupTests.ts
new file mode 100644
index 0000000000..9bb3e72355
--- /dev/null
+++ b/plugins/gcalendar-homepage/src/setupTests.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2022 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import '@testing-library/jest-dom';
+import 'cross-fetch/polyfill';
diff --git a/yarn.lock b/yarn.lock
index d432deb3b3..10257a38e7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1302,6 +1302,13 @@
dependencies:
regenerator-runtime "^0.13.4"
+"@babel/runtime@^7.17.0", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2":
+ version "7.17.2"
+ resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.17.2.tgz#66f68591605e59da47523c631416b18508779941"
+ integrity sha512-hzeyJyMA1YGdJTuWU0e/j4wKXrU4OMFvY2MSlaI9B7VQb0r5cxTE3EAIS2Q7Tn2RIcDkRvTA/v2JsAEhxe99uw==
+ dependencies:
+ regenerator-runtime "^0.13.4"
+
"@babel/template@^7.16.7", "@babel/template@^7.3.3":
version "7.16.7"
resolved "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155"
@@ -1793,11 +1800,49 @@
ms "^2.1.3"
secure-json-parse "^2.4.0"
+"@emotion/cache@^11.7.1":
+ version "11.7.1"
+ resolved "https://registry.npmjs.org/@emotion/cache/-/cache-11.7.1.tgz#08d080e396a42e0037848214e8aa7bf879065539"
+ integrity sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==
+ dependencies:
+ "@emotion/memoize" "^0.7.4"
+ "@emotion/sheet" "^1.1.0"
+ "@emotion/utils" "^1.0.0"
+ "@emotion/weak-memoize" "^0.2.5"
+ stylis "4.0.13"
+
"@emotion/hash@^0.8.0":
version "0.8.0"
resolved "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
+"@emotion/is-prop-valid@^1.1.1":
+ version "1.1.2"
+ resolved "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.1.2.tgz#34ad6e98e871aa6f7a20469b602911b8b11b3a95"
+ integrity sha512-3QnhqeL+WW88YjYbQL5gUIkthuMw7a0NGbZ7wfFVk2kg/CK5w8w5FFa0RzWjyY1+sujN0NWbtSHH6OJmWHtJpQ==
+ dependencies:
+ "@emotion/memoize" "^0.7.4"
+
+"@emotion/memoize@^0.7.4":
+ version "0.7.5"
+ resolved "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50"
+ integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==
+
+"@emotion/sheet@^1.1.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.1.0.tgz#56d99c41f0a1cda2726a05aa6a20afd4c63e58d2"
+ integrity sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==
+
+"@emotion/utils@^1.0.0":
+ version "1.1.0"
+ resolved "https://registry.npmjs.org/@emotion/utils/-/utils-1.1.0.tgz#86b0b297f3f1a0f2bdb08eeac9a2f49afd40d0cf"
+ integrity sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==
+
+"@emotion/weak-memoize@^0.2.5":
+ version "0.2.5"
+ resolved "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
+ integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
+
"@endemolshinegroup/cosmiconfig-typescript-loader@3.0.2":
version "3.0.2"
resolved "https://registry.npmjs.org/@endemolshinegroup/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-3.0.2.tgz#eea4635828dde372838b0909693ebd9aafeec22d"
@@ -4004,6 +4049,13 @@
prop-types "^15.7.2"
react-is "^16.8.0 || ^17.0.0"
+"@maxim_mazurok/gapi.client.calendar@latest":
+ version "3.0.20220211"
+ resolved "https://registry.npmjs.org/@maxim_mazurok/gapi.client.calendar/-/gapi.client.calendar-3.0.20220211.tgz#20125706c761c75219de15788cee100b7933fec6"
+ integrity sha512-ehK4pF4pJXjKFEXp+uiteq1hLAcf28ZbCxgzc6jL9XpNFDSpbhY2iPGU4lkWLS3ae4qrlScFCnmExem5Hg1gZQ==
+ dependencies:
+ "@types/gapi.client" "*"
+
"@microsoft/api-documenter@^7.15.0":
version "7.15.0"
resolved "https://registry.npmjs.org/@microsoft/api-documenter/-/api-documenter-7.15.0.tgz#e6cf24fc0e2f18a71dcf4c5c8100cc083167a81e"
@@ -4089,6 +4141,115 @@
outvariant "^1.2.0"
strict-event-emitter "^0.2.0"
+"@mui/base@5.0.0-alpha.69":
+ version "5.0.0-alpha.69"
+ resolved "https://registry.npmjs.org/@mui/base/-/base-5.0.0-alpha.69.tgz#8511198d760de0795870f5ec63e53db73ba801ec"
+ integrity sha512-IxUUj/lkilCTNBIybQxyQGW/zpxFp490G0QBQJgRp9TJkW2PWSTLvAH7gcH0YHd0L2TAf1TRgfdemoRseMzqQA==
+ dependencies:
+ "@babel/runtime" "^7.17.0"
+ "@emotion/is-prop-valid" "^1.1.1"
+ "@mui/utils" "^5.4.2"
+ "@popperjs/core" "^2.4.4"
+ clsx "^1.1.1"
+ prop-types "^15.7.2"
+ react-is "^17.0.2"
+
+"@mui/icons-material@^5.0.0":
+ version "5.4.2"
+ resolved "https://registry.npmjs.org/@mui/icons-material/-/icons-material-5.4.2.tgz#b2fd2c6c81d2d275e17ce40bd50c63cb197d324b"
+ integrity sha512-7c+G3jBT+e+pN0a9DJ0Bd8Kr1Vy6os5Q1yd2aXcwuhlRI3uzJBLJ8sX6FSWoh5DSEBchb7Bsk1uHz6U0YN9l+Q==
+ dependencies:
+ "@babel/runtime" "^7.17.0"
+
+"@mui/material@^5.0.0":
+ version "5.4.2"
+ resolved "https://registry.npmjs.org/@mui/material/-/material-5.4.2.tgz#04ea6632d7ca600a2ae528f6f140ef0af9c01434"
+ integrity sha512-jmeLWEO6AA6g7HErhI3MXVGaMZtqDZjDwcHCg24WY954wO38Xn0zJ53VfpFc44ZTJLV9Ejd7ci9fLlG/HmJCeg==
+ dependencies:
+ "@babel/runtime" "^7.17.0"
+ "@mui/base" "5.0.0-alpha.69"
+ "@mui/system" "^5.4.2"
+ "@mui/types" "^7.1.2"
+ "@mui/utils" "^5.4.2"
+ "@types/react-transition-group" "^4.4.4"
+ clsx "^1.1.1"
+ csstype "^3.0.10"
+ hoist-non-react-statics "^3.3.2"
+ prop-types "^15.7.2"
+ react-is "^17.0.2"
+ react-transition-group "^4.4.2"
+
+"@mui/private-theming@^5.4.2":
+ version "5.4.2"
+ resolved "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.4.2.tgz#f0a05f908456a2f7b87ccb6fc3b6e1faae9d89e6"
+ integrity sha512-mlPDYYko4wIcwXjCPEmOWbNTT4DZ6h9YHdnRtQPnWM28+TRUHEo7SbydnnmVDQLRXUfaH4Y6XtEHIfBNPE/SLg==
+ dependencies:
+ "@babel/runtime" "^7.17.0"
+ "@mui/utils" "^5.4.2"
+ prop-types "^15.7.2"
+
+"@mui/styled-engine@^5.4.2":
+ version "5.4.2"
+ resolved "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.4.2.tgz#e04903e06bd49fd10072a44ff38e13f5481bb64d"
+ integrity sha512-tz9p3aRtzXHKAg7x3BgP0hVQEoGKaxNCFxsJ+d/iqEHYvywWFSs6oxqYAvDHIRpvMlUZyPNoTrkcNnbdMmH/ng==
+ dependencies:
+ "@babel/runtime" "^7.17.0"
+ "@emotion/cache" "^11.7.1"
+ prop-types "^15.7.2"
+
+"@mui/styles@^5.0.0":
+ version "5.4.2"
+ resolved "https://registry.npmjs.org/@mui/styles/-/styles-5.4.2.tgz#e0dadfc5de8255605f23c2f909f3669f0911bb88"
+ integrity sha512-BX75fNHmRF51yove9dBkH28gpSFjClOPDEnUwLTghPYN913OsqViS/iuCd61dxzygtEEmmeYuWfQjxu/F6vF5g==
+ dependencies:
+ "@babel/runtime" "^7.17.0"
+ "@emotion/hash" "^0.8.0"
+ "@mui/private-theming" "^5.4.2"
+ "@mui/types" "^7.1.2"
+ "@mui/utils" "^5.4.2"
+ clsx "^1.1.1"
+ csstype "^3.0.10"
+ hoist-non-react-statics "^3.3.2"
+ jss "^10.8.2"
+ jss-plugin-camel-case "^10.8.2"
+ jss-plugin-default-unit "^10.8.2"
+ jss-plugin-global "^10.8.2"
+ jss-plugin-nested "^10.8.2"
+ jss-plugin-props-sort "^10.8.2"
+ jss-plugin-rule-value-function "^10.8.2"
+ jss-plugin-vendor-prefixer "^10.8.2"
+ prop-types "^15.7.2"
+
+"@mui/system@^5.4.2":
+ version "5.4.2"
+ resolved "https://registry.npmjs.org/@mui/system/-/system-5.4.2.tgz#8166e406ba4628950bd79cec8159de25d5aef162"
+ integrity sha512-QegBVu6fxUNov1X9bWc1MZUTeV3A5g9PIpli7d0kzkGfq6JzrJWuPlhSPZ+6hlWmWky+bbAXhU65Qz8atWxDGw==
+ dependencies:
+ "@babel/runtime" "^7.17.0"
+ "@mui/private-theming" "^5.4.2"
+ "@mui/styled-engine" "^5.4.2"
+ "@mui/types" "^7.1.2"
+ "@mui/utils" "^5.4.2"
+ clsx "^1.1.1"
+ csstype "^3.0.10"
+ prop-types "^15.7.2"
+
+"@mui/types@^7.1.2":
+ version "7.1.2"
+ resolved "https://registry.npmjs.org/@mui/types/-/types-7.1.2.tgz#4f3678ae77a7a3efab73b6e040469cc6df2144ac"
+ integrity sha512-SD7O1nVzqG+ckQpFjDhXPZjRceB8HQFHEvdLLrPhlJy4lLbwEBbxK74Tj4t6Jgk0fTvLJisuwOutrtYe9P/xBQ==
+
+"@mui/utils@^5.4.2":
+ version "5.4.2"
+ resolved "https://registry.npmjs.org/@mui/utils/-/utils-5.4.2.tgz#3edda8f80de235418fff0424ee66e2a49793ec01"
+ integrity sha512-646dBCC57MXTo/Gf3AnZSHRHznaTETQq5x7AWp5FRQ4jPeyT4WSs18cpJVwkV01cAHKh06pNQTIufIALIWCL5g==
+ dependencies:
+ "@babel/runtime" "^7.17.0"
+ "@types/prop-types" "^15.7.4"
+ "@types/react-is" "^16.7.1 || ^17.0.0"
+ prop-types "^15.7.2"
+ react-is "^17.0.2"
+
"@n1ru4l/graphql-live-query@0.9.0", "@n1ru4l/graphql-live-query@^0.9.0":
version "0.9.0"
resolved "https://registry.npmjs.org/@n1ru4l/graphql-live-query/-/graphql-live-query-0.9.0.tgz#defaebdd31f625bee49e6745934f36312532b2bc"
@@ -4677,6 +4838,11 @@
resolved "https://registry.npmjs.org/@panva/asn1.js/-/asn1.js-1.0.0.tgz#dd55ae7b8129e02049f009408b97c61ccf9032f6"
integrity sha512-UdkG3mLEqXgnlKsWanWcgb6dOjUzJ+XC5f+aWw30qrtjxeNUSfKX1cd5FBzOaXQumoe9nIqeZUvrRJS03HCCtw==
+"@popperjs/core@^2.4.4":
+ version "2.11.2"
+ resolved "https://registry.npmjs.org/@popperjs/core/-/core-2.11.2.tgz#830beaec4b4091a9e9398ac50f865ddea52186b9"
+ integrity sha512-92FRmppjjqz29VMJ2dn+xdyXZBrMlE42AV6Kq6BwjWV7CNUW1hs2FtxSNLQE+gJhaZ6AAmYuO9y8dshhcBl7vA==
+
"@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2":
version "1.1.2"
resolved "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf"
@@ -5737,6 +5903,30 @@
dependencies:
"@types/node" "*"
+"@types/gapi.auth2@^0.0.56":
+ version "0.0.56"
+ resolved "https://registry.npmjs.org/@types/gapi.auth2/-/gapi.auth2-0.0.56.tgz#2f7031f79390b8401e7950d8277ada874fd2731c"
+ integrity sha512-kGaBtGVCqGS3Y05L56dGVlBpJflxLfwA0zpMQnQgGRFk1tsMPbQnogG51UQjt1vCuYfRO0Jd9/K5KDtzjAbMkA==
+ dependencies:
+ "@types/gapi" "*"
+
+"@types/gapi.client.calendar@^3.0.10":
+ version "3.0.10"
+ resolved "https://registry.npmjs.org/@types/gapi.client.calendar/-/gapi.client.calendar-3.0.10.tgz#4b089d9af2753a07cf1d46adc83b1a7cedb8e355"
+ integrity sha512-NUStEVbHPOhFsw4cWE2CThe5eKpTlmz+fSu8mvEc7j+IDVNgk1kS4C6hZzBCdlIjFfOzdQM3Cyqkt5kt7ze3kA==
+ dependencies:
+ "@maxim_mazurok/gapi.client.calendar" latest
+
+"@types/gapi.client@*":
+ version "1.0.5"
+ resolved "https://registry.npmjs.org/@types/gapi.client/-/gapi.client-1.0.5.tgz#a6eb97e664fe51656c5b52258bd0afef28c76308"
+ integrity sha512-OTpbBMuzfC4lkvaomxqskI/iWRGW3zOZbDXZLNSyiuswTiSSGgILRLkg0POuZ4EgzEdaYaTlXpnXiCp07ri/Yw==
+
+"@types/gapi@*", "@types/gapi@^0.0.41":
+ version "0.0.41"
+ resolved "https://registry.npmjs.org/@types/gapi/-/gapi-0.0.41.tgz#c477ee4f0951c005869219fd10b456ae2bba437e"
+ integrity sha512-tmHO66z/f91JZCDqinj/nNvQEszsz/hBT4+MvCSKT5sDzl5Ld/oXZ8WaecCBjRLw2uWKUInUHM9MhEXWkOiNjw==
+
"@types/git-url-parse@^9.0.0":
version "9.0.1"
resolved "https://registry.npmjs.org/@types/git-url-parse/-/git-url-parse-9.0.1.tgz#1c7cc89527ca8b5afcf260ead3b0e4e373c43938"
@@ -6189,7 +6379,7 @@
resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.0.tgz#dc85454b953178cc6043df5208b9e949b54a3bc4"
integrity sha512-/rM+sWiuOZ5dvuVzV37sUuklsbg+JPOP8d+nNFlo2ZtfpzPiPvh1/gc8liWOLBqe+sR+ZM7guPaIcTt6UZTo7Q==
-"@types/prop-types@*", "@types/prop-types@^15.7.3":
+"@types/prop-types@*", "@types/prop-types@^15.7.3", "@types/prop-types@^15.7.4":
version "15.7.4"
resolved "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11"
integrity sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==
@@ -6225,6 +6415,13 @@
dependencies:
"@types/react" "*"
+"@types/react-is@^16.7.1 || ^17.0.0":
+ version "17.0.3"
+ resolved "https://registry.npmjs.org/@types/react-is/-/react-is-17.0.3.tgz#2d855ba575f2fc8d17ef9861f084acc4b90a137a"
+ integrity sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==
+ dependencies:
+ "@types/react" "*"
+
"@types/react-redux@^7.1.16":
version "7.1.19"
resolved "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.19.tgz#477bd0a9b01bae6d6bf809418cdfa7d3c16d4c62"
@@ -6270,6 +6467,13 @@
dependencies:
"@types/react" "*"
+"@types/react-transition-group@^4.4.4":
+ version "4.4.4"
+ resolved "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.4.tgz#acd4cceaa2be6b757db61ed7b432e103242d163e"
+ integrity sha512-7gAPz7anVK5xzbeQW9wFBDg7G++aPLAFY0QaSMOou9rJZpbuI58WAuJrgu+qR92l61grlnCUe7AFX8KGahAgug==
+ dependencies:
+ "@types/react" "*"
+
"@types/react-virtualized-auto-sizer@^1.0.1":
version "1.0.1"
resolved "https://registry.npmjs.org/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.1.tgz#b3187dae1dfc4c15880c9cfc5b45f2719ea6ebd4"
@@ -6362,6 +6566,13 @@
dependencies:
rollup-plugin-postcss "*"
+"@types/sanitize-html@^2.6.2":
+ version "2.6.2"
+ resolved "https://registry.npmjs.org/@types/sanitize-html/-/sanitize-html-2.6.2.tgz#9c47960841b9def1e4c9dfebaaab010a3f6e97b9"
+ integrity sha512-7Lu2zMQnmHHQGKXVvCOhSziQMpa+R2hMHFefzbYoYMHeaXR0uXqNeOc3JeQQQ8/6Xa2Br/P1IQTLzV09xxAiUQ==
+ dependencies:
+ htmlparser2 "^6.0.0"
+
"@types/scheduler@*":
version "0.16.1"
resolved "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
@@ -7760,6 +7971,13 @@ axios@^0.21.1, axios@^0.21.4:
dependencies:
follow-redirects "^1.14.0"
+axios@^0.26.0:
+ version "0.26.0"
+ resolved "https://registry.npmjs.org/axios/-/axios-0.26.0.tgz#9a318f1c69ec108f8cd5f3c3d390366635e13928"
+ integrity sha512-lKoGLMYtHvFrPVt3r+RBMp9nh34N0M8zEfCWqdWZx6phynIEhQqAdydpyBAAG211zlhX9Rgu08cOamy6XjE5Og==
+ dependencies:
+ follow-redirects "^1.14.8"
+
axobject-query@^2.2.0:
version "2.2.0"
resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
@@ -8044,6 +8262,11 @@ bfj@^7.0.2:
hoopy "^0.1.4"
tryer "^1.0.1"
+big-integer@^1.6.16:
+ version "1.6.51"
+ resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.51.tgz#0df92a5d9880560d3ff2d5fd20245c889d130686"
+ integrity sha512-GPEid2Y9QU1Exl1rpO9B2IPJGHPSupF5GnVIP0blYvNOMer2bTvSWs1jGOUg04hTmu67nmLsQ9TBo1puaotBHg==
+
big-integer@^1.6.17:
version "1.6.48"
resolved "https://registry.npmjs.org/big-integer/-/big-integer-1.6.48.tgz#8fd88bd1632cba4a1c8c3e3d7159f08bb95b4b9e"
@@ -8235,6 +8458,20 @@ breakword@^1.0.5:
dependencies:
wcwidth "^1.0.1"
+broadcast-channel@^3.4.1:
+ version "3.7.0"
+ resolved "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz#2dfa5c7b4289547ac3f6705f9c00af8723889937"
+ integrity sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==
+ dependencies:
+ "@babel/runtime" "^7.7.2"
+ detect-node "^2.1.0"
+ js-sha3 "0.8.0"
+ microseconds "0.2.0"
+ nano-time "1.0.0"
+ oblivious-set "1.0.0"
+ rimraf "3.0.2"
+ unload "2.2.0"
+
brorand@^1.0.1, brorand@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f"
@@ -9009,7 +9246,7 @@ cloneable-readable@^1.0.0:
process-nextick-args "^2.0.0"
readable-stream "^2.3.5"
-clsx@^1.0.2, clsx@^1.0.4:
+clsx@^1.0.2, clsx@^1.0.4, clsx@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/clsx/-/clsx-1.1.1.tgz#98b3134f9abbdf23b2663491ace13c5c03a73188"
integrity sha512-6/bPho624p3S2pMyvP5kKBPXnI3ufHLObBFCfgx+LkeR5lg2XYy2hqZqUf45ypD8COn2bhgGJSUE+l5dhNBieA==
@@ -10008,6 +10245,11 @@ csstype@^2.5.2, csstype@^2.6.7:
resolved "https://registry.npmjs.org/csstype/-/csstype-2.6.17.tgz#4cf30eb87e1d1a005d8b6510f95292413f6a1c0e"
integrity sha512-u1wmTI1jJGzCJzWndZo8mk4wnPTZd1eOIYTYvuEyOQGfmDl3TrabCCfKnOC86FZwW/9djqTl933UF/cS425i9A==
+csstype@^3.0.10:
+ version "3.0.10"
+ resolved "https://registry.npmjs.org/csstype/-/csstype-3.0.10.tgz#2ad3a7bed70f35b965707c092e5f30b327c290e5"
+ integrity sha512-2u44ZG2OcNUO9HDp/Jl8C07x6pU/eTR3ncV91SiK3dhG9TWvRVsCoJw14Ckx5DgWkzGA3waZWO3d7pgqpUI/XA==
+
csstype@^3.0.2, csstype@^3.0.6:
version "3.0.7"
resolved "https://registry.npmjs.org/csstype/-/csstype-3.0.7.tgz#2a5fb75e1015e84dd15692f71e89a1450290950b"
@@ -10711,6 +10953,11 @@ detect-node@^2.0.4:
resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c"
integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==
+detect-node@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz#c9c70775a49c3d03bc2c06d9a73be550f978f8b1"
+ integrity sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==
+
detect-port-alt@^1.1.6:
version "1.1.6"
resolved "https://registry.npmjs.org/detect-port-alt/-/detect-port-alt-1.1.6.tgz#24707deabe932d4a3cf621302027c2b266568275"
@@ -12588,6 +12835,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.14.0:
resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc"
integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==
+follow-redirects@^1.14.8:
+ version "1.14.9"
+ resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz#dd4ea157de7bfaf9ea9b3fbd85aa16951f78d8d7"
+ integrity sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==
+
for-in@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"
@@ -13800,7 +14052,7 @@ html-webpack-plugin@^5.3.1:
pretty-error "^4.0.0"
tapable "^2.0.0"
-htmlparser2@^6.1.0:
+htmlparser2@^6.0.0, htmlparser2@^6.1.0:
version "6.1.0"
resolved "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz#c4d762b6c3371a05dbe65e94ae43a9f845fb8fb7"
integrity sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==
@@ -15588,6 +15840,11 @@ js-levenshtein@^1.1.6:
resolved "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d"
integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==
+js-sha3@0.8.0:
+ version "0.8.0"
+ resolved "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840"
+ integrity sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==
+
"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
@@ -15972,6 +16229,15 @@ jss-plugin-camel-case@^10.5.1:
hyphenate-style-name "^1.0.3"
jss "10.6.0"
+jss-plugin-camel-case@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.npmjs.org/jss-plugin-camel-case/-/jss-plugin-camel-case-10.9.0.tgz#4921b568b38d893f39736ee8c4c5f1c64670aaf7"
+ integrity sha512-UH6uPpnDk413/r/2Olmw4+y54yEF2lRIV8XIZyuYpgPYTITLlPOsq6XB9qeqv+75SQSg3KLocq5jUBXW8qWWww==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ hyphenate-style-name "^1.0.3"
+ jss "10.9.0"
+
jss-plugin-default-unit@^10.5.1:
version "10.6.0"
resolved "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.6.0.tgz#af47972486819b375f0f3a9e0213403a84b5ef3b"
@@ -15980,6 +16246,14 @@ jss-plugin-default-unit@^10.5.1:
"@babel/runtime" "^7.3.1"
jss "10.6.0"
+jss-plugin-default-unit@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.npmjs.org/jss-plugin-default-unit/-/jss-plugin-default-unit-10.9.0.tgz#bb23a48f075bc0ce852b4b4d3f7582bc002df991"
+ integrity sha512-7Ju4Q9wJ/MZPsxfu4T84mzdn7pLHWeqoGd/D8O3eDNNJ93Xc8PxnLmV8s8ZPNRYkLdxZqKtm1nPQ0BM4JRlq2w==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.0"
+
jss-plugin-global@^10.5.1:
version "10.6.0"
resolved "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.6.0.tgz#3e8011f760f399cbadcca7f10a485b729c50e3ed"
@@ -15988,6 +16262,14 @@ jss-plugin-global@^10.5.1:
"@babel/runtime" "^7.3.1"
jss "10.6.0"
+jss-plugin-global@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.npmjs.org/jss-plugin-global/-/jss-plugin-global-10.9.0.tgz#fc07a0086ac97aca174e37edb480b69277f3931f"
+ integrity sha512-4G8PHNJ0x6nwAFsEzcuVDiBlyMsj2y3VjmFAx/uHk/R/gzJV+yRHICjT4MKGGu1cJq2hfowFWCyrr/Gg37FbgQ==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.0"
+
jss-plugin-nested@^10.5.1:
version "10.6.0"
resolved "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.6.0.tgz#5f83c5c337d3b38004834e8426957715a0251641"
@@ -15997,6 +16279,15 @@ jss-plugin-nested@^10.5.1:
jss "10.6.0"
tiny-warning "^1.0.2"
+jss-plugin-nested@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.npmjs.org/jss-plugin-nested/-/jss-plugin-nested-10.9.0.tgz#cc1c7d63ad542c3ccc6e2c66c8328c6b6b00f4b3"
+ integrity sha512-2UJnDrfCZpMYcpPYR16oZB7VAC6b/1QLsRiAutOt7wJaaqwCBvNsosLEu/fUyKNQNGdvg2PPJFDO5AX7dwxtoA==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.0"
+ tiny-warning "^1.0.2"
+
jss-plugin-props-sort@^10.5.1:
version "10.6.0"
resolved "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.6.0.tgz#297879f35f9fe21196448579fee37bcde28ce6bc"
@@ -16005,6 +16296,14 @@ jss-plugin-props-sort@^10.5.1:
"@babel/runtime" "^7.3.1"
jss "10.6.0"
+jss-plugin-props-sort@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.npmjs.org/jss-plugin-props-sort/-/jss-plugin-props-sort-10.9.0.tgz#30e9567ef9479043feb6e5e59db09b4de687c47d"
+ integrity sha512-7A76HI8bzwqrsMOJTWKx/uD5v+U8piLnp5bvru7g/3ZEQOu1+PjHvv7bFdNO3DwNPC9oM0a//KwIJsIcDCjDzw==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.0"
+
jss-plugin-rule-value-function@^10.5.1:
version "10.6.0"
resolved "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.6.0.tgz#3c1a557236a139d0151e70a82c810ccce1c1c5ea"
@@ -16014,6 +16313,15 @@ jss-plugin-rule-value-function@^10.5.1:
jss "10.6.0"
tiny-warning "^1.0.2"
+jss-plugin-rule-value-function@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.npmjs.org/jss-plugin-rule-value-function/-/jss-plugin-rule-value-function-10.9.0.tgz#379fd2732c0746fe45168011fe25544c1a295d67"
+ integrity sha512-IHJv6YrEf8pRzkY207cPmdbBstBaE+z8pazhPShfz0tZSDtRdQua5jjg6NMz3IbTasVx9FdnmptxPqSWL5tyJg==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ jss "10.9.0"
+ tiny-warning "^1.0.2"
+
jss-plugin-vendor-prefixer@^10.5.1:
version "10.6.0"
resolved "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.6.0.tgz#e1fcd499352846890c38085b11dbd7aa1c4f2c78"
@@ -16023,6 +16331,15 @@ jss-plugin-vendor-prefixer@^10.5.1:
css-vendor "^2.0.8"
jss "10.6.0"
+jss-plugin-vendor-prefixer@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.npmjs.org/jss-plugin-vendor-prefixer/-/jss-plugin-vendor-prefixer-10.9.0.tgz#aa9df98abfb3f75f7ed59a3ec50a5452461a206a"
+ integrity sha512-MbvsaXP7iiVdYVSEoi+blrW+AYnTDvHTW6I6zqi7JcwXdc6I9Kbm234nEblayhF38EftoenbM+5218pidmC5gA==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ css-vendor "^2.0.8"
+ jss "10.9.0"
+
jss@10.6.0, jss@^10.5.1:
version "10.6.0"
resolved "https://registry.npmjs.org/jss/-/jss-10.6.0.tgz#d92ff9d0f214f65ca1718591b68e107be4774149"
@@ -16034,6 +16351,16 @@ jss@10.6.0, jss@^10.5.1:
is-in-browser "^1.1.3"
tiny-warning "^1.0.2"
+jss@10.9.0, jss@^10.8.2:
+ version "10.9.0"
+ resolved "https://registry.npmjs.org/jss/-/jss-10.9.0.tgz#7583ee2cdc904a83c872ba695d1baab4b59c141b"
+ integrity sha512-YpzpreB6kUunQBbrlArlsMpXYyndt9JATbt95tajx0t4MTJJcCJdd4hdNpHmOIDiUJrF/oX5wtVFrS3uofWfGw==
+ dependencies:
+ "@babel/runtime" "^7.3.1"
+ csstype "^3.0.2"
+ is-in-browser "^1.1.3"
+ tiny-warning "^1.0.2"
+
"jsx-ast-utils@^2.4.1 || ^3.0.0":
version "3.2.0"
resolved "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.2.0.tgz#41108d2cec408c3453c1bbe8a4aae9e1e2bd8f82"
@@ -16910,7 +17237,7 @@ lunr@^2.3.9:
resolved "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1"
integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow==
-luxon@^2.0.2:
+luxon@^2.0.2, luxon@^2.3.0:
version "2.3.0"
resolved "https://registry.npmjs.org/luxon/-/luxon-2.3.0.tgz#bf16a7e642513c2a20a6230a6a41b0ab446d0045"
integrity sha512-gv6jZCV+gGIrVKhO90yrsn8qXPKD8HYZJtrUDSfEbow8Tkw84T9OnCyJhWvnJIaIF/tBuiAjZuQHUt1LddX2mg==
@@ -17079,6 +17406,26 @@ marked@^4.0.10:
resolved "https://registry.npmjs.org/marked/-/marked-4.0.10.tgz#423e295385cc0c3a70fa495e0df68b007b879423"
integrity sha512-+QvuFj0nGgO970fySghXGmuw+Fd0gD2x3+MqCWLIPf5oxdv1Ka6b2q+z9RP01P/IaKPMEramy+7cNy/Lw8c3hw==
+match-sorter@^6.0.2:
+ version "6.3.1"
+ resolved "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.1.tgz#98cc37fda756093424ddf3cbc62bfe9c75b92bda"
+ integrity sha512-mxybbo3pPNuA+ZuCUhm5bwNkXrJTbsk5VWbR5wiwz/GC6LIiegBGn2w3O08UG/jdbYLinw51fSQ5xNU1U3MgBw==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+ remove-accents "0.4.2"
+
+material-ui-popup-state@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.npmjs.org/material-ui-popup-state/-/material-ui-popup-state-2.0.0.tgz#8050421a9d00df67d7f63e7b2b73ab5b331051ff"
+ integrity sha512-1sbb9xpMs7OxG0SOGfGO0ZnwiLtqZSoXda0/AqqJkpouT4e0nADXutQtDJFMa9GUMNAODVDlYnNmfqM+MhFjsg==
+ dependencies:
+ "@babel/runtime" "^7.12.5"
+ "@mui/icons-material" "^5.0.0"
+ "@mui/material" "^5.0.0"
+ "@mui/styles" "^5.0.0"
+ classnames "^2.2.6"
+ prop-types "^15.7.2"
+
material-ui-search-bar@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/material-ui-search-bar/-/material-ui-search-bar-1.0.0.tgz#2652dd5bdc4cb043cffb7144d9c296c120702e62"
@@ -17684,6 +18031,11 @@ micromatch@^4.0.2, micromatch@^4.0.4:
braces "^3.0.1"
picomatch "^2.2.3"
+microseconds@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz#233b25f50c62a65d861f978a4a4f8ec18797dc39"
+ integrity sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==
+
miller-rabin@^4.0.0:
version "4.0.1"
resolved "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d"
@@ -18134,6 +18486,13 @@ nano-css@^5.3.1:
stacktrace-js "^2.0.2"
stylis "^4.0.6"
+nano-time@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz#b0554f69ad89e22d0907f7a12b0993a5d96137ef"
+ integrity sha1-sFVPaa2J4i0JB/ehKwmTpdlhN+8=
+ dependencies:
+ big-integer "^1.6.16"
+
nanoclone@^0.2.1:
version "0.2.1"
resolved "https://registry.npmjs.org/nanoclone/-/nanoclone-0.2.1.tgz#dd4090f8f1a110d26bb32c49ed2f5b9235209ed4"
@@ -18770,6 +19129,11 @@ object.values@^1.1.5:
define-properties "^1.1.3"
es-abstract "^1.19.1"
+oblivious-set@1.0.0:
+ version "1.0.0"
+ resolved "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz#c8316f2c2fb6ff7b11b6158db3234c49f733c566"
+ integrity sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==
+
obuf@^1.0.0, obuf@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e"
@@ -19329,6 +19693,11 @@ parse-path@^4.0.0:
is-ssh "^1.3.0"
protocols "^1.4.0"
+parse-srcset@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1"
+ integrity sha1-8r0iH2zJcKk42IVWq8WJyqqiveE=
+
parse-url@^5.0.0:
version "5.0.1"
resolved "https://registry.npmjs.org/parse-url/-/parse-url-5.0.1.tgz#99c4084fc11be14141efa41b3d117a96fcb9527f"
@@ -20141,7 +20510,7 @@ postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0, postcss-value-parser@^
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
-postcss@^8.1.0, postcss@^8.4.5:
+postcss@^8.1.0, postcss@^8.3.11, postcss@^8.4.5:
version "8.4.6"
resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.6.tgz#c5ff3c3c457a23864f32cb45ac9b741498a09ae1"
integrity sha512-OovjwIzs9Te46vlEx7+uXB0PLijpwjXGKXjVGGPIGubGpq7uh5Xgf6D6FiJ/SzJMBosHDp6a2hiXOS97iBXcaA==
@@ -20842,7 +21211,7 @@ react-is@^16.10.2, react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0, react
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
-"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.0, react-is@^17.0.1:
+"react-is@^16.8.0 || ^17.0.0", react-is@^17.0.0, react-is@^17.0.1, react-is@^17.0.2:
version "17.0.2"
resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
@@ -20872,6 +21241,15 @@ react-markdown@^8.0.0:
unist-util-visit "^4.0.0"
vfile "^5.0.0"
+react-query@^3.34.16:
+ version "3.34.16"
+ resolved "https://registry.npmjs.org/react-query/-/react-query-3.34.16.tgz#279ea180bcaeaec49c7864b29d1711ee9f152594"
+ integrity sha512-7FvBvjgEM4YQ8nPfmAr+lJfbW95uyW/TVjFoi2GwCkF33/S8ajx45tuPHPFGWs4qYwPy1mzwxD4IQfpUDrefNQ==
+ dependencies:
+ "@babel/runtime" "^7.5.5"
+ broadcast-channel "^3.4.1"
+ match-sorter "^6.0.2"
+
react-redux@^7.1.1, react-redux@^7.2.4:
version "7.2.5"
resolved "https://registry.npmjs.org/react-redux/-/react-redux-7.2.5.tgz#213c1b05aa1187d9c940ddfc0b29450957f6a3b8"
@@ -21004,6 +21382,16 @@ react-transition-group@^4.0.0, react-transition-group@^4.4.0:
loose-envify "^1.4.0"
prop-types "^15.6.2"
+react-transition-group@^4.4.2:
+ version "4.4.2"
+ resolved "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.2.tgz#8b59a56f09ced7b55cbd53c36768b922890d5470"
+ integrity sha512-/RNYfRAMlZwDSr6z4zNKV6xu53/e2BuaBbGhbyYIXTrmgu/bGHzmqOs7mJSJBHy9Ud+ApHx3QjrkKSp1pxvlFg==
+ dependencies:
+ "@babel/runtime" "^7.5.5"
+ dom-helpers "^5.0.1"
+ loose-envify "^1.4.0"
+ prop-types "^15.6.2"
+
react-universal-interface@^0.6.2:
version "0.6.2"
resolved "https://registry.npmjs.org/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b"
@@ -21608,6 +21996,11 @@ remedial@^1.0.7:
resolved "https://registry.npmjs.org/remedial/-/remedial-1.0.8.tgz#a5e4fd52a0e4956adbaf62da63a5a46a78c578a0"
integrity sha512-/62tYiOe6DzS5BqVsNpH/nkGlX45C/Sp6V+NtiN6JQNS1Viay7cWkazmRkrQrdFj2eshDe96SIQNIoMxqhzBOg==
+remove-accents@0.4.2:
+ version "0.4.2"
+ resolved "https://registry.npmjs.org/remove-accents/-/remove-accents-0.4.2.tgz#0a43d3aaae1e80db919e07ae254b285d9e1c7bb5"
+ integrity sha1-CkPTqq4egNuRngeuJUsoXZ4ce7U=
+
remove-trailing-separator@^1.0.1:
version "1.1.0"
resolved "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
@@ -23354,6 +23747,11 @@ stylehacks@^5.0.1:
browserslist "^4.16.0"
postcss-selector-parser "^6.0.4"
+stylis@4.0.13:
+ version "4.0.13"
+ resolved "https://registry.npmjs.org/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91"
+ integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
+
stylis@^4.0.6:
version "4.0.7"
resolved "https://registry.npmjs.org/stylis/-/stylis-4.0.7.tgz#412a90c28079417f3d27c028035095e4232d2904"
@@ -24636,6 +25034,14 @@ unixify@1.0.0, unixify@^1.0.0:
dependencies:
normalize-path "^2.1.1"
+unload@2.2.0:
+ version "2.2.0"
+ resolved "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz#ccc88fdcad345faa06a92039ec0f80b488880ef7"
+ integrity sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==
+ dependencies:
+ "@babel/runtime" "^7.6.2"
+ detect-node "^2.0.4"
+
unpipe@1.0.0, unpipe@~1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"