diff --git a/.gitignore b/.gitignore index 17568606..126df069 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,6 @@ yarn-error.log* # vercel .vercel + +# Webstorm +.idea diff --git a/calendso.yaml b/calendso.yaml index 8f052f87..d45d0f7d 100644 --- a/calendso.yaml +++ b/calendso.yaml @@ -110,6 +110,17 @@ paths: summary: Deletes an event type tags: - Availability + /api/availability/calendars: + post: + description: Selects calendar for availability checking. + summary: Adds selected calendar + tags: + - Availability + delete: + description: Removes a calendar from availability checking. + summary: Deletes a selected calendar + tags: + - Availability /api/book/:user: post: description: Creates a booking in the user's calendar. @@ -144,4 +155,4 @@ paths: description: Updates a user's profile. summary: Updates a user's profile tags: - - User \ No newline at end of file + - User diff --git a/components/ActiveLink.tsx b/components/ActiveLink.tsx index f4da7d65..d7ae61c3 100644 --- a/components/ActiveLink.tsx +++ b/components/ActiveLink.tsx @@ -1,5 +1,4 @@ import { useRouter } from 'next/router' -import PropTypes from 'prop-types' import Link from 'next/link' import React, { Children } from 'react' diff --git a/components/Settings.tsx b/components/Settings.tsx index ff23e35b..bfbfd19c 100644 --- a/components/Settings.tsx +++ b/components/Settings.tsx @@ -1,5 +1,5 @@ import ActiveLink from '../components/ActiveLink'; -import { UserCircleIcon, KeyIcon, CodeIcon, UserGroupIcon } from '@heroicons/react/outline'; +import { UserCircleIcon, KeyIcon, CodeIcon, UserGroupIcon, CreditCardIcon } from '@heroicons/react/outline'; export default function SettingsShell(props) { return ( @@ -37,6 +37,11 @@ export default function SettingsShell(props) { Teams + {/* Change/remove me, if you're self-hosting */} + + Billing + + {/*

Loading...

; - } + const [showSelectCalendarModal, setShowSelectCalendarModal] = useState(false); + const [selectableCalendars, setSelectableCalendars] = useState([]); function toggleAddModal() { setShowAddModal(!showAddModal); } + function toggleShowCalendarModal() { + setShowSelectCalendarModal(!showSelectCalendarModal); + } + + function loadCalendars() { + fetch('api/availability/calendar') + .then((response) => response.json()) + .then(data => { + setSelectableCalendars(data) + }); + } + function integrationHandler(type) { fetch('/api/integrations/' + type.replace('_', '') + '/add') .then((response) => response.json()) .then((data) => window.location.href = data.url); } + function calendarSelectionHandler(calendar) { + return (selected) => { + let cals = [...selectableCalendars]; + let i = cals.findIndex(c => c.externalId === calendar.externalId); + cals[i].selected = selected; + setSelectableCalendars(cals); + if (selected) { + fetch('api/availability/calendar', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(cals[i]) + }).then((response) => response.json()); + } else { + fetch('api/availability/calendar', { + method: 'DELETE', headers: { + 'Content-Type': 'application/json' + }, body: JSON.stringify(cals[i]) + }).then((response) => response.json()); + } + } + } + + function getCalendarIntegrationImage(integrationType: string){ + switch (integrationType) { + case "google_calendar": return "integrations/google-calendar.png"; + case "office365_calendar": return "integrations/office-365.png"; + default: return ""; + } + } + + function classNames(...classes) { + return classes.filter(Boolean).join(' ') + } + + useEffect(loadCalendars, [integrations]); + + if (loading) { + return

Loading...

; + } + return (
@@ -39,7 +92,7 @@ export default function Home({ integrations }) { Add new integration
-
+
{integrations.filter( (ig) => ig.credential ).length !== 0 ?
    {integrations.filter(ig => ig.credential).map( (ig) => (
  • @@ -165,6 +218,104 @@ export default function Home({ integrations }) {
} +
+
+

+ Select calendars +

+
+

+ Select which calendars are checked for availability to prevent double bookings. +

+
+
+ +
+
+
+ {showSelectCalendarModal && +
+
+ {/* */} + + + {/* */} +
+
+
+ +
+
+ +
+

+ If no entry is selected, all calendars will be checked +

+
+
+
+
+
    + {selectableCalendars.map( (calendar) => (
  • +
    + {calendar.integration} +
    +
    +

    { calendar.name }

    +
    +
    + + Select calendar + +
    +
  • ))} +
+
+
+ +
+
+
+
+ } ); @@ -225,4 +376,4 @@ export async function getServerSideProps(context) { return { props: {integrations}, } -} \ No newline at end of file +} diff --git a/pages/settings/billing.tsx b/pages/settings/billing.tsx new file mode 100644 index 00000000..57acc64f --- /dev/null +++ b/pages/settings/billing.tsx @@ -0,0 +1,66 @@ +import Head from 'next/head'; +import Shell from '../../components/Shell'; +import SettingsShell from '../../components/Settings'; +import prisma from '../../lib/prisma'; +import {getSession, useSession} from 'next-auth/client'; + +export default function Billing(props) { + const [ session, loading ] = useSession(); + + if (loading) { + return

Loading...

; + } + + return ( + + + Billing | Calendso + + +
+
+

+ Change your Subscription +

+

+ Cancel, update credit card or change plan +

+
+
+