Fixed linting errors

This commit is contained in:
nicolas 2021-07-20 20:18:26 +02:00
commit e0abbbb2f1
7 changed files with 519 additions and 442 deletions

View file

@ -1,9 +1,8 @@
import { GiftIcon } from "@heroicons/react/outline";
export default function DonateBanner() {
if (location.hostname.endsWith(".calendso.com")) {
return null;
}
if (location.hostname.endsWith(".calendso.com")) {
return null;
}
return (
<>
@ -17,21 +16,19 @@ return null;
<GiftIcon className="h-6 w-6 text-white" aria-hidden="true" />
</span>
<p className="ml-3 font-medium text-white truncate">
<span className="md:hidden">
Support the ongoing development
</span>
<span className="md:hidden">Support the ongoing development</span>
<span className="hidden md:inline">
You're using the free self-hosted version. Support the
ongoing development by making a donation.
You&apos;re using the free self-hosted version. Support the ongoing development by making
a donation.
</span>
</p>
</div>
<div className="order-3 mt-2 flex-shrink-0 w-full sm:order-2 sm:mt-0 sm:w-auto">
<a
target="_blank"
rel="noreferrer"
href="https://calendso.com/donate"
className="flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-blue-600 bg-white hover:bg-blue-50"
>
className="flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-blue-600 bg-white hover:bg-blue-50">
Donate
</a>
</div>

View file

@ -2,9 +2,6 @@
@apply font-medium;
} */
.text {
}
.text--body {
@apply text-lg leading-relaxed;
}

View file

@ -1,16 +1,20 @@
const withTM = require('next-transpile-modules')(['react-timezone-select']);
import nextTranspileModules from "next-transpile-modules";
const withTM = nextTranspileModules(["react-timezone-select"]);
// TODO: Revisit this later with getStaticProps in App
if (process.env.NEXTAUTH_URL) {
process.env.BASE_URL = process.env.NEXTAUTH_URL.replace('/api/auth', '');
process.env.BASE_URL = process.env.NEXTAUTH_URL.replace("/api/auth", "");
}
if ( ! process.env.EMAIL_FROM ) {
console.warn('\x1b[33mwarn', '\x1b[0m', 'EMAIL_FROM environment variable is not set, this may indicate mailing is currently disabled. Please refer to the .env.example file.');
if (!process.env.EMAIL_FROM) {
console.warn(
"\x1b[33mwarn",
"\x1b[0m",
"EMAIL_FROM environment variable is not set, this may indicate mailing is currently disabled. Please refer to the .env.example file."
);
}
if (process.env.BASE_URL) {
process.env.NEXTAUTH_URL = process.env.BASE_URL + '/api/auth';
process.env.NEXTAUTH_URL = process.env.BASE_URL + "/api/auth";
}
const validJson = (jsonString) => {
@ -19,13 +23,18 @@ const validJson = (jsonString) => {
if (o && typeof o === "object") {
return o;
}
} catch (e) {
console.error(e);
}
catch (e) {}
return false;
}
};
if (process.env.GOOGLE_API_CREDENTIALS && ! validJson(process.env.GOOGLE_API_CREDENTIALS)) {
console.warn('\x1b[33mwarn', '\x1b[0m', "- Disabled 'Google Calendar' integration. Reason: Invalid value for GOOGLE_API_CREDENTIALS environment variable. When set, this value needs to contain valid JSON like {\"web\":{\"client_id\":\"<clid>\",\"client_secret\":\"<secret>\",\"redirect_uris\":[\"<yourhost>/api/integrations/googlecalendar/callback>\"]}. You can download this JSON from your OAuth Client @ https://console.cloud.google.com/apis/credentials.");
if (process.env.GOOGLE_API_CREDENTIALS && !validJson(process.env.GOOGLE_API_CREDENTIALS)) {
console.warn(
"\x1b[33mwarn",
"\x1b[0m",
'- Disabled \'Google Calendar\' integration. Reason: Invalid value for GOOGLE_API_CREDENTIALS environment variable. When set, this value needs to contain valid JSON like {"web":{"client_id":"<clid>","client_secret":"<secret>","redirect_uris":["<yourhost>/api/integrations/googlecalendar/callback>"]}. You can download this JSON from your OAuth Client @ https://console.cloud.google.com/apis/credentials.'
);
}
module.exports = withTM({
@ -38,10 +47,10 @@ module.exports = withTM({
async redirects() {
return [
{
source: '/settings',
destination: '/settings/profile',
source: "/settings",
destination: "/settings/profile",
permanent: true,
}
]
}
},
];
},
});

View file

@ -12,7 +12,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
if (req.method == "PATCH" || req.method == "POST") {
const data = {
title: req.body.title,
slug: req.body.slug,
slug: req.body.slug.trim(),
description: req.body.description,
length: parseInt(req.body.length),
hidden: req.body.hidden,

View file

@ -1,16 +1,22 @@
import Head from 'next/head';
import Link from 'next/link';
import { CheckIcon } from '@heroicons/react/outline';
import Head from "next/head";
import Link from "next/link";
import { CheckIcon } from "@heroicons/react/outline";
export default function Logout() {
return (
<div className="fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
<div
className="fixed z-10 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<Head>
<title>Logged out - Calendso</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
<div className="inline-block align-bottom bg-white rounded-lg px-4 pt-5 pb-4 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-sm sm:w-full sm:p-6">
<div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
@ -18,12 +24,10 @@ export default function Logout() {
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
You've been logged out
You&apos;ve been logged out
</h3>
<div className="mt-2">
<p className="text-sm text-gray-500">
We hope to see you again soon!
</p>
<p className="text-sm text-gray-500">We hope to see you again soon!</p>
</div>
</div>
</div>

View file

@ -155,7 +155,7 @@ export default function Availability(props) {
</thead>
<tbody className="bg-white divide-y divide-gray-200">
{props.types.map((eventType) => (
<tr key={eventType}>
<tr key={eventType.id}>
<td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900 align-top">
{eventType.title}
{eventType.hidden && (
@ -341,7 +341,7 @@ export default function Availability(props) {
Hide this event type
</label>
<p className="text-gray-500">
Hide the event type from your page, so it can only be booked through its URL.
Hide the event type from your page, so it can only be booked through it&apos;s URL.
</p>
</div>
</div>

View file

@ -1,15 +1,21 @@
import Head from 'next/head';
import Link from 'next/link';
import prisma from '../../lib/prisma';
import Shell from '../../components/Shell';
import {useEffect, useState} from 'react';
import {getSession, useSession} from 'next-auth/client';
import {CalendarIcon, CheckCircleIcon, ChevronRightIcon, PlusIcon, XCircleIcon} from '@heroicons/react/solid';
import {InformationCircleIcon} from '@heroicons/react/outline';
import {Switch} from '@headlessui/react'
import Head from "next/head";
import Link from "next/link";
import prisma from "../../lib/prisma";
import Shell from "../../components/Shell";
import { useEffect, useState } from "react";
import { getSession, useSession } from "next-auth/client";
import {
CalendarIcon,
CheckCircleIcon,
ChevronRightIcon,
PlusIcon,
XCircleIcon,
} from "@heroicons/react/solid";
import { InformationCircleIcon } from "@heroicons/react/outline";
import { Switch } from "@headlessui/react";
export default function Home({ integrations }) {
const [session, loading] = useSession();
const [, loading] = useSession();
const [showAddModal, setShowAddModal] = useState(false);
const [showSelectCalendarModal, setShowSelectCalendarModal] = useState(false);
const [selectableCalendars, setSelectableCalendars] = useState([]);
@ -23,53 +29,58 @@ export default function Home({ integrations }) {
}
function loadCalendars() {
fetch('api/availability/calendar')
fetch("api/availability/calendar")
.then((response) => response.json())
.then(data => {
setSelectableCalendars(data)
.then((data) => {
setSelectableCalendars(data);
});
}
function integrationHandler(type) {
fetch('/api/integrations/' + type.replace('_', '') + '/add')
fetch("/api/integrations/" + type.replace("_", "") + "/add")
.then((response) => response.json())
.then((data) => window.location.href = data.url);
.then((data) => (window.location.href = data.url));
}
function calendarSelectionHandler(calendar) {
return (selected) => {
let cals = [...selectableCalendars];
let i = cals.findIndex(c => c.externalId === calendar.externalId);
const cals = [...selectableCalendars];
const i = cals.findIndex((c) => c.externalId === calendar.externalId);
cals[i].selected = selected;
setSelectableCalendars(cals);
if (selected) {
fetch('api/availability/calendar', {
method: 'POST',
fetch("api/availability/calendar", {
method: "POST",
headers: {
'Content-Type': 'application/json'
"Content-Type": "application/json",
},
body: JSON.stringify(cals[i])
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])
fetch("api/availability/calendar", {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(cals[i]),
}).then((response) => response.json());
}
}
};
}
function getCalendarIntegrationImage(integrationType: string){
function getCalendarIntegrationImage(integrationType: string) {
switch (integrationType) {
case "google_calendar": return "integrations/google-calendar.png";
case "office365_calendar": return "integrations/office-365.png";
default: return "";
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(' ')
return classes.filter(Boolean).join(" ");
}
useEffect(loadCalendars, [integrations]);
@ -87,14 +98,20 @@ export default function Home({ integrations }) {
<Shell heading="Integrations" noPaddingBottom>
<div className="text-right py-2">
<button onClick={toggleAddModal} type="button"
<button
onClick={toggleAddModal}
type="button"
className="px-4 py-2 border border-gray-300 shadow-sm text-sm font-medium rounded-md text-gray-700 bg-white hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500">
Add new integration
</button>
</div>
<div className="bg-white shadow overflow-hidden rounded-lg mb-8">
{integrations.filter( (ig) => ig.credential ).length !== 0 ? <ul className="divide-y divide-gray-200">
{integrations.filter(ig => ig.credential).map( (ig) => (<li>
{integrations.filter((ig) => ig.credential).length !== 0 ? (
<ul className="divide-y divide-gray-200">
{integrations
.filter((ig) => ig.credential)
.map((ig) => (
<li key={ig.id}>
<Link href={"/integrations/" + ig.credential.id}>
<a className="block hover:bg-gray-50">
<div className="flex items-center px-4 py-4 sm:px-6">
@ -106,19 +123,27 @@ export default function Home({ integrations }) {
<div>
<p className="text-sm font-medium text-blue-600 truncate">{ig.title}</p>
<p className="flex items-center text-sm text-gray-500">
{ig.type.endsWith('_calendar') && <span className="truncate">Calendar Integration</span>}
{ig.type.endsWith('_video') && <span className="truncate">Video Conferencing</span>}
{ig.type.endsWith("_calendar") && (
<span className="truncate">Calendar Integration</span>
)}
{ig.type.endsWith("_video") && (
<span className="truncate">Video Conferencing</span>
)}
</p>
</div>
<div className="hidden md:block">
{ig.credential.key && <p className="mt-2 flex items-center text text-gray-500">
{ig.credential.key && (
<p className="mt-2 flex items-center text text-gray-500">
<CheckCircleIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-green-400" />
Connected
</p>}
{!ig.credential.key && <p className="mt-3 flex items-center text text-gray-500">
</p>
)}
{!ig.credential.key && (
<p className="mt-3 flex items-center text text-gray-500">
<XCircleIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-yellow-400" />
Not connected
</p>}
</p>
)}
</div>
</div>
<div>
@ -128,9 +153,10 @@ export default function Home({ integrations }) {
</div>
</a>
</Link>
</li>))}
</li>
))}
</ul>
:
) : (
<div className="bg-white shadow rounded-lg">
<div className="flex">
<div className="py-9 pl-8">
@ -138,23 +164,33 @@ export default function Home({ integrations }) {
</div>
<div className="py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">
You don't have any integrations added.
You don&apos;t have any integrations added.
</h3>
<div className="mt-2 text-sm text-gray-500">
<p>
You currently do not have any integrations set up. Add your first integration to get started.
You currently do not have any integrations set up. Add your first integration to get
started.
</p>
</div>
<div className="mt-3 text-sm">
<button onClick={toggleAddModal} className="font-medium text-blue-600 hover:text-blue-500"> Add your first integration <span aria-hidden="true">&rarr;</span></button>
<button
onClick={toggleAddModal}
className="font-medium text-blue-600 hover:text-blue-500">
{" "}
Add your first integration <span aria-hidden="true">&rarr;</span>
</button>
</div>
</div>
</div>
</div>
}
)}
</div>
{showAddModal &&
<div className="fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
{showAddModal && (
<div
className="fixed z-10 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
{/* <!--
Background overlay, show/hide based on modal state.
@ -166,8 +202,12 @@ export default function Home({ integrations }) {
From: "opacity-100"
To: "opacity-0"
--> */}
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
{/* <!--
Modal panel, show/hide based on modal state.
@ -188,46 +228,55 @@ export default function Home({ integrations }) {
Add a new integration
</h3>
<div>
<p className="text-sm text-gray-400">
Link a new integration to your account.
</p>
<p className="text-sm text-gray-400">Link a new integration to your account.</p>
</div>
</div>
</div>
<div className="my-4">
<ul className="divide-y divide-gray-200">
{integrations.filter( (integration) => integration.installed ).map( (integration) => (<li className="flex py-4">
{integrations
.filter((integration) => integration.installed)
.map((integration) => (
<li key={integration.type} className="flex py-4">
<div className="w-1/12 mr-4 pt-2">
<img className="h-8 w-8 mr-2" src={integration.imageSrc} alt={integration.title} />
<img
className="h-8 w-8 mr-2"
src={integration.imageSrc}
alt={integration.title}
/>
</div>
<div className="w-10/12">
<h2 className="text-gray-800 font-medium">{ integration.title }</h2>
<p className="text-gray-400 text-sm">{ integration.description }</p>
<h2 className="text-gray-800 font-medium">{integration.title}</h2>
<p className="text-gray-400 text-sm">{integration.description}</p>
</div>
<div className="w-2/12 text-right pt-2">
<button onClick={() => integrationHandler(integration.type)} className="font-medium text-blue-600 hover:text-blue-500">Add</button>
<button
onClick={() => integrationHandler(integration.type)}
className="font-medium text-blue-600 hover:text-blue-500">
Add
</button>
</div>
</li>))}
</li>
))}
</ul>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button onClick={toggleAddModal} type="button" className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm">
<button
onClick={toggleAddModal}
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm">
Close
</button>
</div>
</div>
</div>
</div>
}
)}
<div className="bg-white shadow rounded-lg">
<div className="px-4 py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">
Select calendars
</h3>
<h3 className="text-lg leading-6 font-medium text-gray-900">Select calendars</h3>
<div className="mt-2 max-w-xl text-sm text-gray-500">
<p>
Select which calendars are checked for availability to prevent double bookings.
</p>
<p>Select which calendars are checked for availability to prevent double bookings.</p>
</div>
<div className="mt-5">
<button type="button" onClick={toggleShowCalendarModal} className="btn btn-primary">
@ -236,8 +285,12 @@ export default function Home({ integrations }) {
</div>
</div>
</div>
{showSelectCalendarModal &&
<div className="fixed z-10 inset-0 overflow-y-auto" aria-labelledby="modal-title" role="dialog" aria-modal="true">
{showSelectCalendarModal && (
<div
className="fixed z-10 inset-0 overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div className="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0">
{/* <!--
Background overlay, show/hide based on modal state.
@ -249,8 +302,12 @@ export default function Home({ integrations }) {
From: "opacity-100"
To: "opacity-0"
--> */}
<div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">&#8203;</span>
<div
className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity"
aria-hidden="true"></div>
<span className="hidden sm:inline-block sm:align-middle sm:h-screen" aria-hidden="true">
&#8203;
</span>
{/* <!--
Modal panel, show/hide based on modal state.
@ -279,44 +336,52 @@ export default function Home({ integrations }) {
</div>
<div className="my-4">
<ul className="divide-y divide-gray-200">
{selectableCalendars.map( (calendar) => (<li className="flex py-4">
{selectableCalendars.map((calendar) => (
<li key={calendar.name} className="flex py-4">
<div className="w-1/12 mr-4 pt-2">
<img className="h-8 w-8 mr-2" src={getCalendarIntegrationImage(calendar.integration)} alt={calendar.integration} />
<img
className="h-8 w-8 mr-2"
src={getCalendarIntegrationImage(calendar.integration)}
alt={calendar.integration}
/>
</div>
<div className="w-10/12 pt-3">
<h2 className="text-gray-800 font-medium">{ calendar.name }</h2>
<h2 className="text-gray-800 font-medium">{calendar.name}</h2>
</div>
<div className="w-2/12 text-right pt-3">
<Switch
checked={calendar.selected}
onChange={calendarSelectionHandler(calendar)}
className={classNames(
calendar.selected ? 'bg-indigo-600' : 'bg-gray-200',
'relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500'
)}
>
calendar.selected ? "bg-indigo-600" : "bg-gray-200",
"relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
)}>
<span className="sr-only">Select calendar</span>
<span
aria-hidden="true"
className={classNames(
calendar.selected ? 'translate-x-5' : 'translate-x-0',
'pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200'
calendar.selected ? "translate-x-5" : "translate-x-0",
"pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"
)}
/>
</Switch>
</div>
</li>))}
</li>
))}
</ul>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button onClick={toggleShowCalendarModal} type="button" className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm">
<button
onClick={toggleShowCalendarModal}
type="button"
className="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:mt-0 sm:w-auto sm:text-sm">
Close
</button>
</div>
</div>
</div>
</div>
}
)}
</Shell>
</div>
);
@ -328,23 +393,24 @@ const validJson = (jsonString: string) => {
if (o && typeof o === "object") {
return o;
}
} catch (e) {
console.error(e);
}
catch (e) {}
return false;
}
};
export async function getServerSideProps(context) {
const session = await getSession(context);
if (!session) {
return { redirect: { permanent: false, destination: '/auth/login' } };
return { redirect: { permanent: false, destination: "/auth/login" } };
}
const user = await prisma.user.findFirst({
where: {
email: session.user.email,
},
select: {
id: true
}
id: true,
},
});
const credentials = await prisma.credential.findMany({
@ -354,34 +420,38 @@ export async function getServerSideProps(context) {
select: {
id: true,
type: true,
key: true
}
key: true,
},
});
const integrations = [ {
const integrations = [
{
installed: !!(process.env.GOOGLE_API_CREDENTIALS && validJson(process.env.GOOGLE_API_CREDENTIALS)),
credential: credentials.find( (integration) => integration.type === "google_calendar" ) || null,
credential: credentials.find((integration) => integration.type === "google_calendar") || null,
type: "google_calendar",
title: "Google Calendar",
imageSrc: "integrations/google-calendar.png",
description: "For personal and business calendars",
}, {
},
{
installed: !!(process.env.MS_GRAPH_CLIENT_ID && process.env.MS_GRAPH_CLIENT_SECRET),
type: "office365_calendar",
credential: credentials.find( (integration) => integration.type === "office365_calendar" ) || null,
credential: credentials.find((integration) => integration.type === "office365_calendar") || null,
title: "Office 365 / Outlook.com Calendar",
imageSrc: "integrations/office-365.png",
description: "For personal and business calendars",
}, {
},
{
installed: !!(process.env.ZOOM_CLIENT_ID && process.env.ZOOM_CLIENT_SECRET),
type: "zoom_video",
credential: credentials.find( (integration) => integration.type === "zoom_video" ) || null,
credential: credentials.find((integration) => integration.type === "zoom_video") || null,
title: "Zoom",
imageSrc: "integrations/zoom.png",
description: "Video Conferencing",
} ];
},
];
return {
props: {integrations},
}
props: { integrations },
};
}