Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@
"command": "maildev -s 587",
"isBackground": false,
"problemMatcher": []
},
{
"label": "AppStoreWatch",
"type": "shell",
"command": "yarn app-store:watch",
"isBackground": false,
"problemMatcher": []
}
]
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,7 +402,7 @@ Next make sure you have your app running `yarn dx`. Then in the slack chat type
4. Fill in any information you want in the "App info" tab
5. Go to tab "Auth"
6. Now copy the Client ID and Client Secret to your .env file into the `HUBSPOT_CLIENT_ID` and `HUBSPOT_CLIENT_SECRET` fields.
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/hubspot othercalendar/callback` replacing Cal.com URL with the URI at which your application runs.
7. Set the Redirect URL for OAuth `<Cal.com URL>/api/integrations/hubspotothercalendar/callback` replacing Cal.com URL with the URI at which your application runs.
8. In the "Scopes" section at the bottom of the page, make sure you select "Read" and "Write" for scope called `crm.objects.contacts`
9. Click the "Save" button at the bottom footer.
10. You're good to go. Now you can see any booking in Cal.com created as a meeting in HubSpot for your contacts.
Expand Down
8 changes: 4 additions & 4 deletions apps/web/components/AdditionalCalendarSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { OptionProps } from "react-select";

import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { App } from "@calcom/types/App";
import type { AppMeta } from "@calcom/types/App";
import { Button } from "@calcom/ui";

import { QueryCell } from "@lib/QueryCell";
Expand All @@ -14,11 +14,11 @@ interface AdditionalCalendarSelectorProps {
isLoading?: boolean;
}

const ImageOption = (optionProps: OptionProps<{ [key: string]: string; type: App["type"] }>) => {
const ImageOption = (optionProps: OptionProps<{ [key: string]: string; appId: string }>) => {
const { data } = optionProps;
return (
<InstallAppButton
type={data.type}
slug={data.appId}
render={(installProps) => {
return (
<Button {...installProps} className="w-full" color="minimal">
Expand Down Expand Up @@ -46,7 +46,7 @@ const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorPro
label: item.name,
slug: item.slug,
image: item.imageSrc,
type: item.type,
appId: item.appId,
}));
return (
<Select
Expand Down
17 changes: 10 additions & 7 deletions apps/web/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import React, { useEffect, useState } from "react";

import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { App as AppType } from "@calcom/types/App";
import { AppMeta as AppType } from "@calcom/types/App";
import { Button, SkeletonButton } from "@calcom/ui";

import Shell from "@components/Shell";
Expand All @@ -23,6 +23,7 @@ import Badge from "@components/ui/Badge";
export default function App({
name,
type,
slug,
logo,
body,
categories,
Expand All @@ -38,6 +39,7 @@ export default function App({
privacy,
}: {
name: string;
slug: string;
type: AppType["type"];
isGlobal?: AppType["isGlobal"];
logo: string;
Expand All @@ -64,9 +66,9 @@ export default function App({
const [installedApp, setInstalledApp] = useState(0);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
async function getInstalledApp(appCredentialType: string) {
async function getInstalledApp(slug: string) {
const queryParam = new URLSearchParams();
queryParam.set("app-credential-type", appCredentialType);
queryParam.set("app-slug", slug);
try {
const result = await fetch(`/api/app-store/installed?${queryParam.toString()}`, {
method: "GET",
Expand All @@ -87,8 +89,9 @@ export default function App({
}
}
}
getInstalledApp(type);
}, [type]);
getInstalledApp(slug);
}, [slug]);

return (
<>
<Shell large isPublic>
Expand Down Expand Up @@ -123,7 +126,7 @@ export default function App({
: t("globally_install")}
</Button>
<InstallAppButton
type={type}
slug={slug}
render={(buttonProps) => (
<Button StartIcon={PlusIcon} data-testid="install-app-button" {...buttonProps}>
{t("add_another")}
Expand All @@ -133,7 +136,7 @@ export default function App({
</div>
) : (
<InstallAppButton
type={type}
slug={slug}
render={(buttonProps) => (
<Button data-testid="install-app-button" {...buttonProps}>
{t("install_app")}
Expand Down
4 changes: 2 additions & 2 deletions apps/web/components/apps/AllApps.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { App } from "@calcom/types/App";
import type { AppMeta } from "@calcom/types/App";

import AppCard from "./AppCard";

export default function AllApps({ apps }: { apps: App[] }) {
export default function AllApps({ apps }: { apps: AppMeta[] }) {
const { t } = useLocale();

return (
Expand Down
4 changes: 2 additions & 2 deletions apps/web/components/apps/TrendingAppsSlider.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { App } from "@calcom/types/App";
import type { AppMeta } from "@calcom/types/App";

import AppCard from "./AppCard";
import Slider from "./Slider";

const TrendingAppsSlider = <T extends App>({ items }: { items: T[] }) => {
const TrendingAppsSlider = <T extends AppMeta>({ items }: { items: T[] }) => {
const { t } = useLocale();

return (
Expand Down
2 changes: 1 addition & 1 deletion apps/web/components/integrations/CalendarListContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ function ConnectedCalendarsList(props: Props) {
key={cal.externalId}
externalId={cal.externalId}
title={cal.name || "Nameless calendar"}
type={item.integration.type}
type={item.integration.appId}
defaultSelected={cal.isSelected}
/>
))}
Expand Down
6 changes: 5 additions & 1 deletion apps/web/ee/pages/api/integrations/stripepayment/webhook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ async function handlePaymentSuccess(event: Stripe.Event) {
user: {
select: {
id: true,
credentials: true,
credentials: {
include: {
app: true,
},
},
timeZone: true,
email: true,
name: true,
Expand Down
26 changes: 20 additions & 6 deletions apps/web/pages/api/app-store/installed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,32 @@ import { getSession } from "@lib/auth";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
req.session = await getSession({ req });
if (req.method === "GET" && req.session && req.session.user.id && req.query) {
const { "app-credential-type": appCredentialType } = req.query;
if (!appCredentialType && Array.isArray(appCredentialType)) {
const { "app-slug": appSlug } = req.query;
if (!appSlug || typeof appSlug !== "string") {
return res.status(400);
}

const userId = req.session.user.id;
let where;
if (appSlug === "giphy") {
where = {
userId: userId,
type: "giphy_other",
};
} else if (appSlug === "slack") {
where = {
userId: userId,
type: "slack_app",
};
} else {
where = {
userId: userId,
appId: appSlug,
};
}
try {
const installedApp = await prisma.credential.findMany({
where: {
type: appCredentialType as string,
userId: userId,
},
where,
});

if (installedApp && !!installedApp.length) {
Expand Down
3 changes: 3 additions & 0 deletions apps/web/pages/api/book/confirm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
select: {
id: true,
credentials: {
include: {
app: true,
},
orderBy: { id: "desc" as Prisma.SortOrder },
},
timeZone: true,
Expand Down
6 changes: 5 additions & 1 deletion apps/web/pages/api/book/event.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@ const userSelect = Prisma.validator<Prisma.UserArgs>()({
name: true,
username: true,
timeZone: true,
credentials: true,
credentials: {
include: {
app: true,
},
},
bufferTime: true,
destinationCalendar: true,
locale: true,
Expand Down
13 changes: 7 additions & 6 deletions apps/web/pages/api/integrations/[...args].ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { NextApiHandler, NextApiRequest, NextApiResponse } from "next";

import { deriveAppKeyFromSlugOrType } from "@calcom/lib/deriveAppKeyFromSlugOrType";

import { getSession } from "@lib/auth";
import { HttpError } from "@lib/core/http/error";

Expand All @@ -13,15 +15,14 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
return res.status(404).json({ message: `API route not found` });
}

const [_appName, apiEndpoint] = args;
const appName = _appName.split("_").join(""); // Transform `zoom_video` to `zoomvideo`;

const [appName, apiEndpoint] = args;
try {
/* Absolute path didn't work */
const handlerMap = (await import("@calcom/app-store/apiHandlers")).default;
const handlers = await handlerMap[appName as keyof typeof handlerMap];
const handler = handlers[apiEndpoint as keyof typeof handlers] as NextApiHandler;
const handlerMap = (await import("@calcom/app-store/apps.generated")).apiHandlers;

const handlerKey = deriveAppKeyFromSlugOrType(appName, handlerMap);
const handlers = await handlerMap[handlerKey];
const handler = handlers[apiEndpoint as keyof typeof handlers] as NextApiHandler;
if (typeof handler !== "function")
throw new HttpError({ statusCode: 404, message: `API handler not found` });

Expand Down
15 changes: 11 additions & 4 deletions apps/web/pages/apps/[slug]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import Image from "next/image";
import Link from "next/link";
import path from "path";

import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import { getAppRegistry, getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import prisma from "@calcom/prisma";

import useMediaQuery from "@lib/hooks/useMediaQuery";
Expand Down Expand Up @@ -49,6 +49,7 @@ const components = {
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
return (
<App
slug={data.slug}
name={data.name}
isGlobal={data.isGlobal}
type={data.type}
Expand Down Expand Up @@ -87,8 +88,14 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {

if (!app) return { notFound: true };

const singleApp = await getAppWithMetadata(app);

let singleApp = await getAppWithMetadata(app);
const appStoreFromDb = await getAppRegistry();
appStoreFromDb.forEach((appFromDb) => {
singleApp = {
...singleApp,
...appFromDb,
};
});
if (!singleApp) return { notFound: true };

const appDirname = app.dirName;
Expand All @@ -97,7 +104,7 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
let source = "";

try {
/* If the app doesn't have a README we fallback to the packagfe description */
/* If the app doesn't have a README we fallback to the package description */
source = fs.readFileSync(postFilePath).toString();
} catch (error) {
console.log(`No README.mdx provided for: ${appDirname}`);
Expand Down
12 changes: 6 additions & 6 deletions apps/web/pages/apps/installed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { JSONObject } from "superjson/dist/types";
import { InstallAppButton } from "@calcom/app-store/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import showToast from "@calcom/lib/notification";
import type { App } from "@calcom/types/App";
import type { AppMeta } from "@calcom/types/App";
import { Alert } from "@calcom/ui/Alert";
import Button from "@calcom/ui/Button";
import EmptyScreen from "@calcom/ui/EmptyScreen";
Expand All @@ -28,13 +28,13 @@ import SubHeadingTitleWithConnections from "@components/integrations/SubHeadingT

function ConnectOrDisconnectIntegrationButton(props: {
credentialIds: number[];
type: App["type"];
slug: AppMeta["slug"];
isGlobal?: boolean;
installed?: boolean;
}) {
const { t } = useLocale();
const [credentialId] = props.credentialIds;
const type = props.type;
const slug = props.slug;
const utils = trpc.useContext();
const handleOpenChange = () => {
utils.invalidateQueries(["viewer.integrations"]);
Expand Down Expand Up @@ -83,7 +83,7 @@ function ConnectOrDisconnectIntegrationButton(props: {
}
return (
<InstallAppButton
type={props.type}
slug={props.slug}
render={(buttonProps) => (
<Button color="secondary" {...buttonProps} data-testid="integration-connection-button">
{t("connect")}
Expand All @@ -95,7 +95,7 @@ function ConnectOrDisconnectIntegrationButton(props: {
}

interface IntegrationsContainerProps {
variant: App["variant"];
variant: AppMeta["variant"];
className?: string;
}

Expand Down Expand Up @@ -127,7 +127,7 @@ const IntegrationsContainer = ({ variant, className = "" }: IntegrationsContaine
actions={
<ConnectOrDisconnectIntegrationButton
credentialIds={item.credentialIds}
type={item.type}
slug={item.appId}
isGlobal={item.isGlobal}
installed
/>
Expand Down
2 changes: 1 addition & 1 deletion apps/web/pages/getting-started.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -708,7 +708,7 @@ export async function getServerSideProps(context: NextPageContext) {
});

const integrations = getApps(credentials)
.filter((item) => item.type.endsWith("_calendar"))
.filter((item) => item.category.includes("calendar"))
.map((item) => omit(item, "key"));

// get user's credentials + their connected integrations
Expand Down
3 changes: 3 additions & 0 deletions apps/web/server/createContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ async function getUserFromSession({
userId: true,
appId: true,
},
include: {
app: true,
},
orderBy: {
id: "asc",
},
Expand Down
Loading