Refactor app store & fix admin apps list bugs (#7812)

* If omni install do not show app successfully installed toast

* Clean up. Up to hubspot

* Fix double click to enable apps

* Clean up rest of apps

* Create cli app add dirName

* Type fix

* Only enable apps if keys are valid

* Save dirName as slug

* Remove dirname from metadata

---------

Co-authored-by: zomars <zomars@me.com>
This commit is contained in:
Joe Au-Yeung 2023-04-11 21:56:43 -04:00 committed by GitHub
parent d61f50724b
commit f6f257c705
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 35 additions and 351 deletions

View File

@ -112,6 +112,7 @@ export const BaseAppFork = {
isTemplate,
// Store the template used to create an app
__template: template,
dirName: slug,
};
const currentConfig = JSON.parse(fs.readFileSync(`${appDirPath}/config.json`).toString());
config = {

View File

@ -73,7 +73,7 @@ function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMuta
if (!isOmniInstall) {
gotoUrl(json.url, json.newTab);
return;
return { setupPending: externalUrl || json.url.endsWith("/setup") };
}
// Skip redirection only if it is an OmniInstall and redirect URL isn't of some other origin
@ -83,7 +83,7 @@ function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMuta
if (externalUrl) {
// TODO: For Omni installation to authenticate and come back to the page where installation was initiated, some changes need to be done in all apps' add callbacks
gotoUrl(json.url, json.newTab);
return;
return { setupPending: externalUrl };
}
return { setupPending: externalUrl || json.url.endsWith("/setup") };

View File

@ -12,5 +12,6 @@
"email": "support@cal.com",
"description": "The joyful productivity app\r\r",
"__createdUsingCli": true,
"dependencies": ["google-calendar"]
"dependencies": ["google-calendar"],
"dirName": "amie"
}

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("apple_calendar");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -5,27 +5,10 @@
import dynamic from "next/dynamic";
export const InstallAppButtonMap = {
applecalendar: dynamic(() => import("./applecalendar/components/InstallAppButton")),
around: dynamic(() => import("./around/components/InstallAppButton")),
caldavcalendar: dynamic(() => import("./caldavcalendar/components/InstallAppButton")),
closecom: dynamic(() => import("./closecom/components/InstallAppButton")),
exchange2013calendar: dynamic(() => import("./exchange2013calendar/components/InstallAppButton")),
exchange2016calendar: dynamic(() => import("./exchange2016calendar/components/InstallAppButton")),
exchangecalendar: dynamic(() => import("./exchangecalendar/components/InstallAppButton")),
googlecalendar: dynamic(() => import("./googlecalendar/components/InstallAppButton")),
hubspot: dynamic(() => import("./hubspot/components/InstallAppButton")),
huddle01video: dynamic(() => import("./huddle01video/components/InstallAppButton")),
jitsivideo: dynamic(() => import("./jitsivideo/components/InstallAppButton")),
larkcalendar: dynamic(() => import("./larkcalendar/components/InstallAppButton")),
office365calendar: dynamic(() => import("./office365calendar/components/InstallAppButton")),
office365video: dynamic(() => import("./office365video/components/InstallAppButton")),
riverside: dynamic(() => import("./riverside/components/InstallAppButton")),
tandemvideo: dynamic(() => import("./tandemvideo/components/InstallAppButton")),
vital: dynamic(() => import("./vital/components/InstallAppButton")),
whereby: dynamic(() => import("./whereby/components/InstallAppButton")),
wipemycalother: dynamic(() => import("./wipemycalother/components/InstallAppButton")),
zapier: dynamic(() => import("./zapier/components/InstallAppButton")),
zoomvideo: dynamic(() => import("./zoomvideo/components/InstallAppButton")),
};
export const AppSettingsComponentsMap = {
"general-app-settings": dynamic(() =>

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("around_video");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -21,7 +21,6 @@ export const metadata = {
url: "https://cal.com/",
verified: true,
email: "ali@cal.com",
dirName: "caldavcalendar",
} as AppMeta;
export default metadata;

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("caldav_calendar");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -21,6 +21,7 @@ export const metadata = {
url: "https://cal.com/",
verified: true,
email: "help@cal.com",
dirName: "caldavcalendar",
} as App;
export * as api from "./api";

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("closecom_other_calendar");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("exchange_calendar");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("google_calendar");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("hubspot_other_calendar");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("huddle01_video");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("jitsi_video");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("lark_calendar");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -20,7 +20,6 @@ export const metadata = {
url: "https://cal.com/",
verified: true,
email: "help@cal.com",
dirName: "office365calendar",
} as AppMeta;
export default metadata;

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("office365_calendar");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -22,5 +22,6 @@
"type": "integrations:office365_video",
"label": "MS Teams"
}
}
},
"dirName": "office365video"
}

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("riverside_video");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("tandem_video");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("whereby_video");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("wipemycal_other");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("zapier_automation");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -1,18 +0,0 @@
import type { InstallAppButtonProps } from "@calcom/app-store/types";
import useAddAppMutation from "../../_utils/useAddAppMutation";
export default function InstallAppButton(props: InstallAppButtonProps) {
const mutation = useAddAppMutation("zoom_video");
return (
<>
{props.render({
onClick() {
mutation.mutate("");
},
loading: mutation.isLoading,
})}
</>
);
}

View File

@ -1 +0,0 @@
export { default as InstallAppButton } from "./InstallAppButton";

View File

@ -49,7 +49,7 @@ const IntegrationContainer = ({
const utils = trpc.useContext();
const [disableDialog, setDisableDialog] = useState(false);
const showKeyModal = () => {
const showKeyModal = (fromEnabled?: boolean) => {
// FIXME: This is preventing the modal from opening for apps that has null keys
if (app.keys) {
handleModelOpen({
@ -58,6 +58,8 @@ const IntegrationContainer = ({
slug: app.slug,
type: app.type,
isOpen: "editKeys",
fromEnabled,
appName: app.name,
});
}
};
@ -99,6 +101,8 @@ const IntegrationContainer = ({
onClick={() => {
if (app.enabled) {
setDisableDialog(true);
} else if (app.keys) {
showKeyModal(true);
} else {
enableAppMutation.mutate({ slug: app.slug, enabled: app.enabled });
}
@ -179,9 +183,12 @@ const EditKeysModal: FC<{
isOpen: boolean;
keys: App["keys"];
handleModelClose: () => void;
fromEnabled?: boolean;
appName?: string;
}> = (props) => {
const utils = trpc.useContext();
const { t } = useLocale();
const { dirName, slug, type, isOpen, keys, handleModelClose } = props;
const { dirName, slug, type, isOpen, keys, handleModelClose, fromEnabled, appName } = props;
const appKeySchema = appKeysSchemas[dirName as keyof typeof appKeysSchemas];
const formMethods = useForm({
@ -190,7 +197,8 @@ const EditKeysModal: FC<{
const saveKeysMutation = trpc.viewer.appsRouter.saveKeys.useMutation({
onSuccess: () => {
showToast(t("keys_have_been_saved"), "success");
showToast(fromEnabled ? t("app_is_enabled", { appName }) : t("keys_have_been_saved"), "success");
utils.viewer.appsRouter.listLocal.invalidate();
handleModelClose();
},
onError: (error) => {
@ -211,6 +219,7 @@ const EditKeysModal: FC<{
type,
keys: values,
dirName,
fromEnabled,
})
}
className="px-4 pb-4">
@ -251,6 +260,8 @@ interface EditModalState extends Pick<App, "keys"> {
dirName: string;
type: string;
slug: string;
fromEnabled?: boolean;
appName?: string;
}
const AdminAppsListContainer = () => {
@ -310,6 +321,8 @@ const AdminAppsListContainer = () => {
isOpen={modalState.isOpen === "editKeys"}
slug={modalState.slug}
type={modalState.type}
fromEnabled={modalState.fromEnabled}
appName={modalState.appName}
/>
</>
);

View File

@ -135,10 +135,11 @@ export const appsRouter = router({
([appMetadata?.category] as AppCategories[]) ||
undefined,
keys: undefined,
enabled: !input.enabled,
},
});
// If disabling an app then we need to alert users basesd on the app type
// If disabling an app then we need to alert users based on the app type
if (input.enabled) {
if (app.categories.some((category) => ["calendar", "video"].includes(category))) {
// Find all users with the app credentials
@ -240,10 +241,12 @@ export const appsRouter = router({
type: z.string(),
// Validate w/ app specific schema
keys: z.unknown(),
fromEnabled: z.boolean().optional(),
})
)
.mutation(async ({ ctx, input }) => {
const appKey = deriveAppDictKeyFromType(input.type, appKeysSchemas);
let appKey = deriveAppDictKeyFromType(input.type, appKeysSchemas);
if (!appKey) appKey = deriveAppDictKeyFromType(input.slug, appKeysSchemas);
const keysSchema = appKeysSchemas[appKey as keyof typeof appKeysSchemas];
const keys = keysSchema.parse(input.keys);
@ -258,7 +261,7 @@ export const appsRouter = router({
where: {
slug: input.slug,
},
update: { keys },
update: { keys, ...(input.fromEnabled && { enabled: true }) },
create: {
slug: input.slug,
dirName: appMetadata?.dirName || appMetadata?.slug || "",
@ -267,6 +270,7 @@ export const appsRouter = router({
([appMetadata?.category] as AppCategories[]) ||
undefined,
keys: (input.keys as Prisma.InputJsonObject) || undefined,
...(input.fromEnabled && { enabled: true }),
},
});
}),

View File

@ -135,6 +135,10 @@ export interface App {
licenseRequired?: boolean;
isProOnly?: boolean;
appData?: AppData;
/**
* @deprecated
* Used only by legacy apps which had slug different from their directory name.
*/
dirName?: string;
isTemplate?: boolean;
__template?: string;