@@ -1,12 +1,12 @@
|
||||
# gcalendar-homepage
|
||||
# gcalendar
|
||||
|
||||
Welcome to the gcalendar-homepage plugin!
|
||||
Welcome to the gcalendar 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).
|
||||
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](http://localhost:3000/gcalendar).
|
||||
|
||||
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.
|
||||
|
||||
@@ -13,7 +13,49 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import React from 'react';
|
||||
import { Content, Page } from '@backstage/core-components';
|
||||
import { googleAuthApiRef } from '@backstage/core-plugin-api';
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { gcalendarPlugin } from '../src/plugin';
|
||||
import { calendarListMock, eventsMock } from './mocks';
|
||||
import { gcalendarPlugin, CalendarCard } from '../src/plugin';
|
||||
import { gcalendarApiRef } from '../src';
|
||||
|
||||
createDevApp().registerPlugin(gcalendarPlugin).render();
|
||||
createDevApp()
|
||||
.registerPlugin(gcalendarPlugin)
|
||||
.registerApi({
|
||||
api: googleAuthApiRef,
|
||||
deps: {},
|
||||
factory: () =>
|
||||
({
|
||||
async getAccessToken() {
|
||||
return Promise.resolve('token');
|
||||
},
|
||||
} as unknown as typeof googleAuthApiRef.T),
|
||||
})
|
||||
.registerApi({
|
||||
api: gcalendarApiRef,
|
||||
deps: {},
|
||||
factory: () =>
|
||||
({
|
||||
async getCalendars() {
|
||||
return Promise.resolve(calendarListMock);
|
||||
},
|
||||
async getEvents() {
|
||||
return Promise.resolve({
|
||||
items: eventsMock,
|
||||
});
|
||||
},
|
||||
} as unknown as typeof gcalendarApiRef.T),
|
||||
})
|
||||
.addPage({
|
||||
element: (
|
||||
<Page themeId="home">
|
||||
<Content>
|
||||
<CalendarCard />
|
||||
</Content>
|
||||
</Page>
|
||||
),
|
||||
title: 'Root Page',
|
||||
})
|
||||
.render();
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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 { GCalendar, GCalendarEvent } from '../src/api';
|
||||
|
||||
const primaryCalendar: GCalendar = {
|
||||
id: 'calendar-1',
|
||||
summary: 'test-1@test.com',
|
||||
primary: true,
|
||||
};
|
||||
const nonPrimaryCalendar: GCalendar = {
|
||||
id: 'calendar-2',
|
||||
summary: 'test-2@test.com',
|
||||
primary: false,
|
||||
backgroundColor: '#9BF0E1',
|
||||
};
|
||||
|
||||
export const calendarListMock = {
|
||||
items: [primaryCalendar, nonPrimaryCalendar],
|
||||
};
|
||||
|
||||
export const eventsMock: GCalendarEvent[] = [...Array(3).keys()].map(i => ({
|
||||
id: i.toString(),
|
||||
summary: `Meeting ${i + 1}`,
|
||||
start: {
|
||||
dateTime: DateTime.now()
|
||||
.minus({ hour: i + 1 })
|
||||
.toISO(),
|
||||
timeZone: 'Europe/London',
|
||||
},
|
||||
end: {
|
||||
dateTime: DateTime.now().minus({ hour: i }).toISO(),
|
||||
timeZone: 'Europe/London',
|
||||
},
|
||||
description: '<h3>Dummy title</h3><p>Dummy description</p>',
|
||||
conferenceData: {
|
||||
entryPoints: [
|
||||
{
|
||||
entryPointType: 'video',
|
||||
uri: 'https://zoom.us/',
|
||||
},
|
||||
],
|
||||
},
|
||||
}));
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 { OAuthApi, createApiRef, FetchApi } from '@backstage/core-plugin-api';
|
||||
|
||||
import { GCalendar, GCalendarEvent } from './types';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
|
||||
type Options = {
|
||||
authApi: OAuthApi;
|
||||
fetchApi: FetchApi;
|
||||
};
|
||||
|
||||
export const gcalendarApiRef = createApiRef<GCalendarApiClient>({
|
||||
id: 'plugin.gcalendar.service',
|
||||
});
|
||||
|
||||
export class GCalendarApiClient {
|
||||
private readonly authApi: OAuthApi;
|
||||
private readonly fetchApi: FetchApi;
|
||||
|
||||
constructor(options: Options) {
|
||||
this.authApi = options.authApi;
|
||||
this.fetchApi = options.fetchApi;
|
||||
}
|
||||
|
||||
private async get<T>(
|
||||
path: string,
|
||||
params: { [key in string]: any },
|
||||
): Promise<T> {
|
||||
const query = new URLSearchParams(params);
|
||||
const url = new URL(
|
||||
`${path}?${query.toString()}`,
|
||||
'https://www.googleapis.com',
|
||||
);
|
||||
const token = await this.authApi.getAccessToken();
|
||||
const response = await this.fetchApi.fetch(url.toString(), {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
public async getCalendars(params?: any) {
|
||||
return this.get<{ items: GCalendar[] }>(
|
||||
'/calendar/v3/users/me/calendarList',
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
public async getEvents(calendarId: string, params?: any) {
|
||||
return this.get<{ items: GCalendarEvent[] }>(
|
||||
`/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events`,
|
||||
params,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -13,61 +13,5 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { OAuthApi, createApiRef, FetchApi } from '@backstage/core-plugin-api';
|
||||
|
||||
import { GCalendar, GCalendarEvent } from '../components/CalendarCard/types';
|
||||
import { ResponseError } from '@backstage/errors';
|
||||
|
||||
type Options = {
|
||||
authApi: OAuthApi;
|
||||
fetchApi: FetchApi;
|
||||
};
|
||||
|
||||
export const gcalendarApiRef = createApiRef<GCalendarApiClient>({
|
||||
id: 'plugin.gcalendar.service',
|
||||
});
|
||||
|
||||
export class GCalendarApiClient {
|
||||
private readonly authApi: OAuthApi;
|
||||
private readonly fetchApi: FetchApi;
|
||||
|
||||
constructor(options: Options) {
|
||||
this.authApi = options.authApi;
|
||||
this.fetchApi = options.fetchApi;
|
||||
}
|
||||
|
||||
private async get<T>(
|
||||
path: string,
|
||||
params: { [key in string]: any },
|
||||
): Promise<T> {
|
||||
const query = new URLSearchParams(params);
|
||||
const url = new URL(
|
||||
`${path}?${query.toString()}`,
|
||||
'https://www.googleapis.com',
|
||||
);
|
||||
const token = await this.authApi.getAccessToken();
|
||||
const response = await this.fetchApi.fetch(url.toString(), {
|
||||
headers: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw await ResponseError.fromResponse(response);
|
||||
}
|
||||
|
||||
return response.json() as Promise<T>;
|
||||
}
|
||||
|
||||
public async getCalendars(params?: any) {
|
||||
return this.get<{ items: GCalendar[] }>(
|
||||
'/calendar/v3/users/me/calendarList',
|
||||
params,
|
||||
);
|
||||
}
|
||||
|
||||
public async getEvents(calendarId: string, params?: any) {
|
||||
return this.get<{ items: GCalendarEvent[] }>(
|
||||
`/calendar/v3/calendars/${encodeURIComponent(calendarId)}/events`,
|
||||
params,
|
||||
);
|
||||
}
|
||||
}
|
||||
export * from './client';
|
||||
export * from './types';
|
||||
|
||||
@@ -18,7 +18,7 @@ import React from 'react';
|
||||
import { renderInTestApp } from '@backstage/test-utils';
|
||||
|
||||
import { AttendeeChip } from './AttendeeChip';
|
||||
import { EventAttendee, ResponseStatus } from './types';
|
||||
import { EventAttendee, ResponseStatus } from '../../api';
|
||||
|
||||
describe('<AttendeeChip />', () => {
|
||||
it('renders attendee email', async () => {
|
||||
|
||||
@@ -21,7 +21,7 @@ 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';
|
||||
import { EventAttendee, ResponseStatus } from '../../api';
|
||||
|
||||
const useStyles = makeStyles((theme: BackstageTheme) => {
|
||||
const getIconColor = (responseStatus?: string) => {
|
||||
|
||||
@@ -35,46 +35,51 @@ import {
|
||||
|
||||
import zoomIcon from '../../icons/zoomIcon.svg';
|
||||
import { CalendarEventPopoverContent } from './CalendarEventPopoverContent';
|
||||
import { GCalendarEvent, ResponseStatus } from './types';
|
||||
import { GCalendarEvent, ResponseStatus } from '../../api';
|
||||
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,
|
||||
const useStyles = makeStyles(
|
||||
theme => ({
|
||||
event: {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginBottom: theme.spacing(1),
|
||||
cursor: 'pointer',
|
||||
paddingRight: 12,
|
||||
},
|
||||
},
|
||||
link: {
|
||||
width: 48,
|
||||
height: 48,
|
||||
display: 'inline-block',
|
||||
padding: 8,
|
||||
borderRadius: '50%',
|
||||
'&:hover': {
|
||||
backgroundColor: theme.palette.grey[100],
|
||||
declined: {
|
||||
textDecoration: 'line-through',
|
||||
},
|
||||
},
|
||||
calendarColor: ({ event }: any) => ({
|
||||
width: 8,
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
backgroundColor: event.primary
|
||||
? theme.palette.primary.light
|
||||
: event.backgroundColor,
|
||||
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 }: { event: GCalendarEvent }) => ({
|
||||
width: 8,
|
||||
borderTopLeftRadius: 4,
|
||||
borderBottomLeftRadius: 4,
|
||||
backgroundColor: event.primary
|
||||
? theme.palette.primary.light
|
||||
: event.backgroundColor,
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
{
|
||||
name: 'GCalendarEvent',
|
||||
},
|
||||
);
|
||||
|
||||
export const CalendarEvent = ({ event }: { event: GCalendarEvent }) => {
|
||||
const classes = useStyles({ event });
|
||||
|
||||
@@ -31,24 +31,29 @@ import {
|
||||
import ArrowForwardIcon from '@material-ui/icons/ArrowForward';
|
||||
|
||||
import { AttendeeChip } from './AttendeeChip';
|
||||
import { GCalendarEvent } from './types';
|
||||
import { GCalendarEvent } from '../../api';
|
||||
import { getTimePeriod, getZoomLink } from './util';
|
||||
|
||||
const useStyles = makeStyles(theme => {
|
||||
return {
|
||||
description: {
|
||||
wordBreak: 'break-word',
|
||||
'& a': {
|
||||
color: theme.palette.primary.main,
|
||||
fontWeight: 500,
|
||||
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),
|
||||
},
|
||||
};
|
||||
});
|
||||
divider: {
|
||||
marginTop: theme.spacing(2),
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
};
|
||||
},
|
||||
{
|
||||
name: 'GCalendarEventPopoverContent',
|
||||
},
|
||||
);
|
||||
|
||||
type CalendarEventPopoverProps = {
|
||||
event: GCalendarEvent;
|
||||
|
||||
@@ -27,17 +27,22 @@ import {
|
||||
makeStyles,
|
||||
} from '@material-ui/core';
|
||||
|
||||
import { GCalendar } from './types';
|
||||
import { GCalendar } from '../../api';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
formControl: {
|
||||
width: 120,
|
||||
const useStyles = makeStyles(
|
||||
{
|
||||
formControl: {
|
||||
width: 120,
|
||||
},
|
||||
selectedCalendars: {
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
},
|
||||
},
|
||||
selectedCalendars: {
|
||||
textOverflow: 'ellipsis',
|
||||
overflow: 'hidden',
|
||||
{
|
||||
name: 'GCalendarSelect',
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
type CalendarSelectProps = {
|
||||
disabled: boolean;
|
||||
|
||||
@@ -18,10 +18,12 @@ import React from 'react';
|
||||
import { Box, Button, styled } from '@material-ui/core';
|
||||
|
||||
import { CalendarEvent } from './CalendarEvent';
|
||||
import { events } from './signInEventMock';
|
||||
import { eventsMock } from './signInEventMock';
|
||||
import { GCalendarEvent } from '../..';
|
||||
|
||||
type SignInContentProps = {
|
||||
type Props = {
|
||||
handleAuthClick: React.MouseEventHandler<HTMLElement>;
|
||||
events?: GCalendarEvent[];
|
||||
};
|
||||
|
||||
const TransparentBox = styled(Box)({
|
||||
@@ -29,7 +31,10 @@ const TransparentBox = styled(Box)({
|
||||
filter: 'blur(1.5px)',
|
||||
});
|
||||
|
||||
export const SignInContent = ({ handleAuthClick }: SignInContentProps) => {
|
||||
export const SignInContent = ({
|
||||
handleAuthClick,
|
||||
events = eventsMock,
|
||||
}: Props) => {
|
||||
return (
|
||||
<Box position="relative" height="100%" width="100%">
|
||||
<TransparentBox p={1}>
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { GCalendarEvent } from './types';
|
||||
import { GCalendarEvent } from '../../api';
|
||||
|
||||
export const events: GCalendarEvent[] = [
|
||||
export const eventsMock: GCalendarEvent[] = [
|
||||
{
|
||||
id: '1',
|
||||
htmlLink: 'https://www.google.com/calendar/',
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { GCalendarEvent } from './types';
|
||||
import { GCalendarEvent } from '../../api';
|
||||
|
||||
export function getZoomLink(event: GCalendarEvent) {
|
||||
const videoEntrypoint = event.conferenceData?.entryPoints?.find(
|
||||
|
||||
@@ -20,7 +20,7 @@ import { useQueries } from 'react-query';
|
||||
import { useApi } from '@backstage/core-plugin-api';
|
||||
|
||||
import { gcalendarApiRef } from '../api';
|
||||
import { GCalendar, GCalendarEvent } from '../components/CalendarCard/types';
|
||||
import { GCalendar, GCalendarEvent } from '../api';
|
||||
|
||||
type Options = {
|
||||
selectedCalendars?: string[];
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
import { gcalendarPlugin } from './plugin';
|
||||
|
||||
describe('gcalendar-homepage', () => {
|
||||
describe('gcalendar', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(gcalendarPlugin).toBeDefined();
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ import { GCalendarApiClient, gcalendarApiRef } from './api';
|
||||
import { rootRouteRef } from './routes';
|
||||
|
||||
export const gcalendarPlugin = createPlugin({
|
||||
id: 'gcalendar-homepage',
|
||||
id: 'gcalendar',
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
},
|
||||
|
||||
@@ -16,5 +16,5 @@
|
||||
import { createRouteRef } from '@backstage/core-plugin-api';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
id: 'gcalendar-homepage',
|
||||
id: 'gcalendar',
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user