Feat/custom default video (#6971)
* Add queries to fetch metadata and update default * Add to zod schema * Change default rules on settings page * Update query * Initall override on create event type * Working default * Change schema to save locationLink too * Update default conferncing to include link * Rename tsx to ts. Fix metadata keys * WIP on refactoring appstore installed * Add this to appstore installed apps too * Display only on conferencing apps * Add i18n strings * Change order in dropdown menu so delete is bottom * Refactor query to not expose all of metadata * Fix mr type checker * Possible e2e fix * Refactor metadata to use ctx * Refactor back to query cell * Alias schema * Adding comment for more info * Remove constant for clarity * Update nextjs-bundle-analysis.yml --------- Co-authored-by: Alex van Andel <me@alexvanandel.com> Co-authored-by: zomars <zomars@me.com>
This commit is contained in:
co-authored by
Alex van Andel
zomars
parent
6cad606ba1
commit
77600f8168
@@ -1,10 +1,12 @@
|
||||
import { useRouter } from "next/router";
|
||||
import { useReducer } from "react";
|
||||
import { useReducer, useState } from "react";
|
||||
import z from "zod";
|
||||
|
||||
import { AppSettings } from "@calcom/app-store/_components/AppSettings";
|
||||
import { InstallAppButton } from "@calcom/app-store/components";
|
||||
import { EventLocationType, getEventLocationTypeFromApp } from "@calcom/app-store/locations";
|
||||
import { InstalledAppVariants } from "@calcom/app-store/utils";
|
||||
import { AppSetDefaultLinkDailog } from "@calcom/features/apps/components/AppSetDefaultLinkDialog";
|
||||
import DisconnectIntegrationModal from "@calcom/features/apps/components/DisconnectIntegrationModal";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||
@@ -22,6 +24,7 @@ import {
|
||||
Dropdown,
|
||||
DropdownMenuItem,
|
||||
DropdownItem,
|
||||
showToast,
|
||||
} from "@calcom/ui";
|
||||
import {
|
||||
FiBarChart,
|
||||
@@ -41,7 +44,7 @@ import AppListCard from "@components/AppListCard";
|
||||
import { CalendarListContainer } from "@components/apps/CalendarListContainer";
|
||||
import InstalledAppsLayout from "@components/apps/layouts/InstalledAppsLayout";
|
||||
|
||||
function ConnectOrDisconnectIntegrationButton(props: {
|
||||
function ConnectOrDisconnectIntegrationMenuItem(props: {
|
||||
credentialIds: number[];
|
||||
type: App["type"];
|
||||
isGlobal?: boolean;
|
||||
@@ -60,22 +63,15 @@ function ConnectOrDisconnectIntegrationButton(props: {
|
||||
|
||||
if (credentialId || type === "stripe_payment" || isGlobal) {
|
||||
return (
|
||||
<Dropdown modal={false}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button StartIcon={FiMoreHorizontal} variant="icon" color="secondary" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
color="destructive"
|
||||
onClick={() => handleDisconnect(credentialId)}
|
||||
disabled={isGlobal}
|
||||
StartIcon={FiTrash}>
|
||||
{t("remove_app")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
color="destructive"
|
||||
onClick={() => handleDisconnect(credentialId)}
|
||||
disabled={isGlobal}
|
||||
StartIcon={FiTrash}>
|
||||
{t("remove_app")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -112,37 +108,95 @@ interface IntegrationsListProps {
|
||||
handleDisconnect: (credentialId: number) => void;
|
||||
}
|
||||
|
||||
const IntegrationsList = ({ data, handleDisconnect }: IntegrationsListProps) => {
|
||||
const IntegrationsList = ({ data, handleDisconnect, variant }: IntegrationsListProps) => {
|
||||
const { data: defaultConferencingApp } = trpc.viewer.getUsersDefaultConferencingApp.useQuery();
|
||||
const utils = trpc.useContext();
|
||||
|
||||
const [locationType, setLocationType] = useState<(EventLocationType & { slug: string }) | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
const updateDefaultAppMutation = trpc.viewer.updateUserDefaultConferencingApp.useMutation({
|
||||
onSuccess: () => {
|
||||
showToast("Default app updated successfully", "success");
|
||||
utils.viewer.getUsersDefaultConferencingApp.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
const { t } = useLocale();
|
||||
return (
|
||||
<List>
|
||||
{data.items
|
||||
.filter((item) => item.invalidCredentialIds)
|
||||
.map((item) => (
|
||||
<AppListCard
|
||||
key={item.name}
|
||||
description={item.description}
|
||||
title={item.name}
|
||||
logo={item.logo}
|
||||
isDefault={item.isGlobal}
|
||||
shouldHighlight
|
||||
slug={item.slug}
|
||||
invalidCredential={item.invalidCredentialIds.length > 0}
|
||||
actions={
|
||||
<div className="flex justify-end">
|
||||
<ConnectOrDisconnectIntegrationButton
|
||||
credentialIds={item.credentialIds}
|
||||
type={item.type}
|
||||
isGlobal={item.isGlobal}
|
||||
installed
|
||||
invalidCredentialIds={item.invalidCredentialIds}
|
||||
handleDisconnect={handleDisconnect}
|
||||
/>
|
||||
</div>
|
||||
}>
|
||||
<AppSettings slug={item.slug} />
|
||||
</AppListCard>
|
||||
))}
|
||||
</List>
|
||||
<>
|
||||
<List>
|
||||
{data.items
|
||||
.filter((item) => item.invalidCredentialIds)
|
||||
.map((item) => {
|
||||
const appSlug = item?.slug;
|
||||
const appIsDefault =
|
||||
appSlug === defaultConferencingApp?.appSlug ||
|
||||
(appSlug === "daily-video" && !defaultConferencingApp?.appSlug);
|
||||
return (
|
||||
<AppListCard
|
||||
key={item.name}
|
||||
description={item.description}
|
||||
title={item.name}
|
||||
logo={item.logo}
|
||||
isDefault={appIsDefault}
|
||||
shouldHighlight
|
||||
slug={item.slug}
|
||||
invalidCredential={item.invalidCredentialIds.length > 0}
|
||||
actions={
|
||||
<div className="flex justify-end">
|
||||
<Dropdown modal={false}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button StartIcon={FiMoreHorizontal} variant="icon" color="secondary" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{!appIsDefault && variant === "conferencing" && (
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
color="secondary"
|
||||
StartIcon={FiVideo}
|
||||
onClick={() => {
|
||||
const locationType = getEventLocationTypeFromApp(
|
||||
item?.locationOption?.value ?? ""
|
||||
);
|
||||
if (locationType?.linkType === "static") {
|
||||
setLocationType({ ...locationType, slug: appSlug });
|
||||
} else {
|
||||
updateDefaultAppMutation.mutate({
|
||||
appSlug,
|
||||
});
|
||||
}
|
||||
}}>
|
||||
{t("change_default_conferencing_app")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<ConnectOrDisconnectIntegrationMenuItem
|
||||
credentialIds={item.credentialIds}
|
||||
type={item.type}
|
||||
isGlobal={item.isGlobal}
|
||||
installed
|
||||
invalidCredentialIds={item.invalidCredentialIds}
|
||||
handleDisconnect={handleDisconnect}
|
||||
/>
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
</div>
|
||||
}>
|
||||
<AppSettings slug={item.slug} />
|
||||
</AppListCard>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
{locationType && (
|
||||
<AppSetDefaultLinkDailog
|
||||
locationType={locationType}
|
||||
setLocationType={() => setLocationType(undefined)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -168,46 +222,43 @@ const IntegrationsContainer = ({
|
||||
query={query}
|
||||
customLoader={<SkeletonLoader />}
|
||||
success={({ data }) => {
|
||||
if (!data.items.length) {
|
||||
return (
|
||||
<EmptyScreen
|
||||
Icon={emptyIcon[variant || "other"]}
|
||||
headline={t("no_category_apps", {
|
||||
category: (variant && t(variant).toLowerCase()) || t("other").toLowerCase(),
|
||||
})}
|
||||
description={t(`no_category_apps_description_${variant || "other"}`)}
|
||||
buttonRaw={
|
||||
<Button
|
||||
color="secondary"
|
||||
data-testid={`connect-${variant || "other"}-apps`}
|
||||
href={variant ? `/apps/categories/${variant}` : "/apps/categories/other"}>
|
||||
{t(`connect_${variant || "other"}_apps`)}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{data.items.length > 0 ? (
|
||||
<div className="rounded-md border border-gray-200 p-7">
|
||||
<ShellSubHeading
|
||||
title={t(variant || "other")}
|
||||
subtitle={t(`installed_app_${variant || "other"}_description`)}
|
||||
className="mb-6"
|
||||
actions={
|
||||
<Button
|
||||
href={
|
||||
variant
|
||||
? `/apps/categories/${variant === "conferencing" ? "video" : variant}`
|
||||
: "/apps"
|
||||
}
|
||||
color="secondary"
|
||||
StartIcon={FiPlus}>
|
||||
{t("add")}
|
||||
</Button>
|
||||
<ShellSubHeading
|
||||
title={t(variant || "other")}
|
||||
subtitle={t(`installed_app_${variant || "other"}_description`)}
|
||||
className="mb-6"
|
||||
actions={
|
||||
<Button
|
||||
href={
|
||||
variant ? `/apps/categories/${variant === "conferencing" ? "video" : variant}` : "/apps"
|
||||
}
|
||||
/>
|
||||
<IntegrationsList handleDisconnect={handleDisconnect} data={data} variant={variant} />
|
||||
</div>
|
||||
) : (
|
||||
<EmptyScreen
|
||||
Icon={emptyIcon[variant || "other"]}
|
||||
headline={t("no_category_apps", {
|
||||
category: (variant && t(variant).toLowerCase()) || t("other").toLowerCase(),
|
||||
})}
|
||||
description={t(`no_category_apps_description_${variant || "other"}`)}
|
||||
buttonRaw={
|
||||
<Button
|
||||
color="secondary"
|
||||
data-testid={`connect-${variant || "other"}-apps`}
|
||||
href={variant ? `/apps/categories/${variant}` : "/apps/categories/other"}>
|
||||
{t(`connect_${variant || "other"}_apps`)}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
color="secondary"
|
||||
StartIcon={FiPlus}>
|
||||
{t("add")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
<IntegrationsList handleDisconnect={handleDisconnect} data={data} variant={variant} />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
@@ -257,17 +308,19 @@ export default function InstalledApps() {
|
||||
return (
|
||||
<>
|
||||
<InstalledAppsLayout heading={t("installed_apps")} subtitle={t("manage_your_connected_apps")}>
|
||||
{categoryList.includes(category) && (
|
||||
<IntegrationsContainer handleDisconnect={handleDisconnect} variant={category} />
|
||||
)}
|
||||
{category === "calendar" && <CalendarListContainer />}
|
||||
{category === "other" && (
|
||||
<IntegrationsContainer
|
||||
handleDisconnect={handleDisconnect}
|
||||
variant={category}
|
||||
exclude={[...categoryList, "calendar"]}
|
||||
/>
|
||||
)}
|
||||
<div className="rounded-md border border-gray-200 p-7">
|
||||
{categoryList.includes(category) && (
|
||||
<IntegrationsContainer handleDisconnect={handleDisconnect} variant={category} />
|
||||
)}
|
||||
{category === "calendar" && <CalendarListContainer />}
|
||||
{category === "other" && (
|
||||
<IntegrationsContainer
|
||||
handleDisconnect={handleDisconnect}
|
||||
variant={category}
|
||||
exclude={[...categoryList, "calendar"]}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</InstalledAppsLayout>
|
||||
<DisconnectIntegrationModal
|
||||
handleModelClose={handleModelClose}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { GetServerSidePropsContext } from "next";
|
||||
import { useState } from "react";
|
||||
|
||||
import { EventLocationType, getEventLocationTypeFromApp } from "@calcom/app-store/locations";
|
||||
import { AppSetDefaultLinkDailog } from "@calcom/features/apps/components/AppSetDefaultLinkDialog";
|
||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
@@ -21,12 +22,10 @@ import {
|
||||
SkeletonContainer,
|
||||
SkeletonText,
|
||||
} from "@calcom/ui";
|
||||
import { FiAlertCircle, FiMoreHorizontal, FiTrash } from "@calcom/ui/components/icon";
|
||||
import { FiAlertCircle, FiMoreHorizontal, FiTrash, FiVideo } from "@calcom/ui/components/icon";
|
||||
|
||||
import AppListCard from "@components/AppListCard";
|
||||
|
||||
import { ssrInit } from "@server/lib/ssr";
|
||||
|
||||
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
|
||||
return (
|
||||
<SkeletonContainer>
|
||||
@@ -43,12 +42,13 @@ const ConferencingLayout = () => {
|
||||
const { t } = useLocale();
|
||||
const utils = trpc.useContext();
|
||||
|
||||
const { data: apps, isLoading } = trpc.viewer.integrations.useQuery(
|
||||
{ variant: "conferencing", onlyInstalled: true },
|
||||
{
|
||||
suspense: true,
|
||||
}
|
||||
);
|
||||
const { data: defaultConferencingApp, isLoading: defaultConferencingAppLoading } =
|
||||
trpc.viewer.getUsersDefaultConferencingApp.useQuery();
|
||||
|
||||
const { data: apps, isLoading } = trpc.viewer.integrations.useQuery({
|
||||
variant: "conferencing",
|
||||
onlyInstalled: true,
|
||||
});
|
||||
const deleteAppMutation = trpc.viewer.deleteCredential.useMutation({
|
||||
onSuccess: () => {
|
||||
showToast("Integration deleted successfully", "success");
|
||||
@@ -61,10 +61,20 @@ const ConferencingLayout = () => {
|
||||
},
|
||||
});
|
||||
|
||||
const updateDefaultAppMutation = trpc.viewer.updateUserDefaultConferencingApp.useMutation({
|
||||
onSuccess: () => {
|
||||
showToast("Default app updated successfully", "success");
|
||||
utils.viewer.getUsersDefaultConferencingApp.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
const [deleteAppModal, setDeleteAppModal] = useState(false);
|
||||
const [locationType, setLocationType] = useState<(EventLocationType & { slug: string }) | undefined>(
|
||||
undefined
|
||||
);
|
||||
const [deleteCredentialId, setDeleteCredentialId] = useState<number>(0);
|
||||
|
||||
if (isLoading)
|
||||
if (isLoading || defaultConferencingAppLoading)
|
||||
return <SkeletonLoader title={t("conferencing")} description={t("conferencing_description")} />;
|
||||
|
||||
return (
|
||||
@@ -74,39 +84,67 @@ const ConferencingLayout = () => {
|
||||
{apps?.items &&
|
||||
apps.items
|
||||
.map((app) => ({ ...app, title: app.title || app.name }))
|
||||
.map((app) => (
|
||||
<AppListCard
|
||||
description={app.description}
|
||||
title={app.title}
|
||||
logo={app.logo}
|
||||
key={app.title}
|
||||
isDefault={app.isGlobal}
|
||||
actions={
|
||||
<div>
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button StartIcon={FiMoreHorizontal} variant="icon" color="secondary" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
color="destructive"
|
||||
disabled={app.isGlobal}
|
||||
StartIcon={FiTrash}
|
||||
onClick={() => {
|
||||
setDeleteCredentialId(app.credentialIds[0]);
|
||||
setDeleteAppModal(true);
|
||||
}}>
|
||||
{t("remove_app")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
.map((app) => {
|
||||
const appSlug = app?.slug;
|
||||
const appIsDefault =
|
||||
appSlug === defaultConferencingApp?.appSlug ||
|
||||
(appSlug === "daily-video" && !defaultConferencingApp?.appSlug); // Default to cal video if the user doesnt have it set (we do this on new account creation but not old)
|
||||
return (
|
||||
<AppListCard
|
||||
description={app.description}
|
||||
title={app.title}
|
||||
logo={app.logo}
|
||||
key={app.title}
|
||||
isDefault={appIsDefault} // @TODO: Handle when a user doesnt have this value set
|
||||
actions={
|
||||
<div>
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button StartIcon={FiMoreHorizontal} variant="icon" color="secondary" />
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{!appIsDefault && (
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
color="secondary"
|
||||
StartIcon={FiVideo}
|
||||
onClick={() => {
|
||||
const locationType = getEventLocationTypeFromApp(
|
||||
app?.locationOption?.value ?? ""
|
||||
);
|
||||
if (locationType?.linkType === "static") {
|
||||
setLocationType({ ...locationType, slug: appSlug });
|
||||
} else {
|
||||
updateDefaultAppMutation.mutate({
|
||||
appSlug,
|
||||
});
|
||||
}
|
||||
}}>
|
||||
{t("change_default_conferencing_app")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
)}
|
||||
<DropdownMenuItem>
|
||||
<DropdownItem
|
||||
type="button"
|
||||
color="destructive"
|
||||
disabled={app.isGlobal}
|
||||
StartIcon={FiTrash}
|
||||
onClick={() => {
|
||||
setDeleteCredentialId(app.credentialIds[0]);
|
||||
setDeleteAppModal(true);
|
||||
}}>
|
||||
{t("remove_app")}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</List>
|
||||
|
||||
<Dialog open={deleteAppModal} onOpenChange={setDeleteAppModal}>
|
||||
@@ -123,20 +161,14 @@ const ConferencingLayout = () => {
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
|
||||
{locationType && (
|
||||
<AppSetDefaultLinkDailog locationType={locationType} setLocationType={setLocationType} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
ConferencingLayout.getLayout = getLayout;
|
||||
|
||||
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
|
||||
const ssr = await ssrInit(context);
|
||||
|
||||
return {
|
||||
props: {
|
||||
trpcState: ssr.dehydrate(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default ConferencingLayout;
|
||||
|
||||
@@ -1589,6 +1589,9 @@
|
||||
"create_a_one_one_template": "Create a one-one one template for an event type and distribute it to multiple members.",
|
||||
"collective_or_roundrobin": "Collective or round-robin",
|
||||
"book_your_team_members": "Book your team members together with collective events or cycle through to get the right person with round-robin.",
|
||||
"default_app_link_title":"Set a default app link",
|
||||
"default_app_link_description":"Setting a default app link allows all newly created event types to use the app link you set.",
|
||||
"change_default_conferencing_app":"Set as default",
|
||||
"under_maintenance": "Down for maintenance",
|
||||
"under_maintenance_description": "The {{appName}} team are performing scheduled maintenance. If you have any questions, please contact support.",
|
||||
"event_type_seats": "{{numberOfSeats}} seats",
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { EventLocationType } from "@calcom/app-store/locations";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { trpc } from "@calcom/trpc/react";
|
||||
import {
|
||||
showToast,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
Form,
|
||||
TextField,
|
||||
DialogFooter,
|
||||
Button,
|
||||
DialogClose,
|
||||
} from "@calcom/ui";
|
||||
import { FiAlertCircle } from "@calcom/ui/components/icon";
|
||||
|
||||
type LocationTypeSetLinkDialogFormProps = {
|
||||
link?: string;
|
||||
type: EventLocationType["type"];
|
||||
};
|
||||
|
||||
export function AppSetDefaultLinkDailog({
|
||||
locationType,
|
||||
setLocationType,
|
||||
}: {
|
||||
locationType: EventLocationType & { slug: string };
|
||||
setLocationType: Dispatch<SetStateAction<(EventLocationType & { slug: string }) | undefined>>;
|
||||
}) {
|
||||
const utils = trpc.useContext();
|
||||
|
||||
const { t } = useLocale();
|
||||
const form = useForm<LocationTypeSetLinkDialogFormProps>({});
|
||||
|
||||
const updateDefaultAppMutation = trpc.viewer.updateUserDefaultConferencingApp.useMutation({
|
||||
onSuccess: () => {
|
||||
showToast("Default app updated successfully", "success");
|
||||
utils.viewer.getUsersDefaultConferencingApp.invalidate();
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog open={!!locationType} onOpenChange={() => setLocationType(undefined)}>
|
||||
<DialogContent
|
||||
title={t("default_app_link_title")}
|
||||
description={t("default_app_link_description")}
|
||||
type="creation"
|
||||
Icon={FiAlertCircle}>
|
||||
<Form
|
||||
form={form}
|
||||
handleSubmit={(values) => {
|
||||
updateDefaultAppMutation.mutate({
|
||||
appSlug: locationType.slug,
|
||||
appLink: values.link,
|
||||
});
|
||||
setLocationType(undefined);
|
||||
}}>
|
||||
<>
|
||||
<TextField
|
||||
type="text"
|
||||
required
|
||||
{...form.register("link")}
|
||||
placeholder={locationType.organizerInputPlaceholder ?? ""}
|
||||
label={locationType.label ?? ""}
|
||||
/>
|
||||
|
||||
<DialogFooter>
|
||||
<Button color="primary" type="submit">
|
||||
{t("save")}
|
||||
</Button>
|
||||
<DialogClose />
|
||||
</DialogFooter>
|
||||
</>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -209,6 +209,12 @@ export const userMetadata = z
|
||||
vitalSettings: vitalSettingsUpdateSchema.optional(),
|
||||
isPremium: z.boolean().optional(),
|
||||
sessionTimeout: z.number().optional(), // Minutes
|
||||
defaultConferencingApp: z
|
||||
.object({
|
||||
appSlug: z.string().default("daily-video").optional(),
|
||||
appLink: z.string().optional(),
|
||||
})
|
||||
.optional(),
|
||||
})
|
||||
.nullable();
|
||||
|
||||
|
||||
@@ -472,7 +472,6 @@ const loggedInViewerRouter = router({
|
||||
const { credentials } = user;
|
||||
|
||||
const enabledApps = await getEnabledApps(credentials);
|
||||
|
||||
let apps = enabledApps.map(
|
||||
({ credentials: _, credential: _1 /* don't leak to frontend */, ...app }) => {
|
||||
const credentialIds = credentials.filter((c) => c.type === app.type).map((c) => c.id);
|
||||
@@ -1181,6 +1180,35 @@ const loggedInViewerRouter = router({
|
||||
});
|
||||
}
|
||||
}),
|
||||
getUsersDefaultConferencingApp: authedProcedure.query(async ({ ctx }) => {
|
||||
return userMetadata.parse(ctx.user.metadata)?.defaultConferencingApp;
|
||||
}),
|
||||
updateUserDefaultConferencingApp: authedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
appSlug: z.string().optional(),
|
||||
appLink: z.string().optional(),
|
||||
})
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const currentMetadata = userMetadata.parse(ctx.user.metadata);
|
||||
|
||||
await ctx.prisma.user.update({
|
||||
where: {
|
||||
id: ctx.user.id,
|
||||
},
|
||||
data: {
|
||||
metadata: {
|
||||
...currentMetadata,
|
||||
defaultConferencingApp: {
|
||||
appSlug: input.appSlug,
|
||||
appLink: input.appLink,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return input;
|
||||
}),
|
||||
});
|
||||
|
||||
export const viewerRouter = mergeRouters(
|
||||
|
||||
+25
-2
@@ -7,6 +7,7 @@ import { z } from "zod";
|
||||
import getAppKeysFromSlug from "@calcom/app-store/_utils/getAppKeysFromSlug";
|
||||
import { DailyLocationType } from "@calcom/app-store/locations";
|
||||
import { stripeDataSchema } from "@calcom/app-store/stripepayment/lib/server";
|
||||
import getApps from "@calcom/app-store/utils";
|
||||
import { validateBookingLimitOrder } from "@calcom/lib";
|
||||
import { CAL_URL } from "@calcom/lib/constants";
|
||||
import getEventTypeById from "@calcom/lib/getEventTypeById";
|
||||
@@ -17,6 +18,7 @@ import {
|
||||
CustomInputSchema,
|
||||
EventTypeMetaDataSchema,
|
||||
stringOrNumber,
|
||||
userMetadata as userMetadataSchema,
|
||||
} from "@calcom/prisma/zod-utils";
|
||||
import { createEventTypeInput } from "@calcom/prisma/zod/custom/eventtype";
|
||||
|
||||
@@ -406,7 +408,29 @@ export const eventTypesRouter = router({
|
||||
create: authedProcedure.input(createEventTypeInput).mutation(async ({ ctx, input }) => {
|
||||
const { schedulingType, teamId, ...rest } = input;
|
||||
const userId = ctx.user.id;
|
||||
// Get Users default conferncing app
|
||||
|
||||
const defaultConferencingData = userMetadataSchema.parse(ctx.user.metadata)?.defaultConferencingApp;
|
||||
const appKeys = await getAppKeysFromSlug("daily-video");
|
||||
|
||||
let locations: { type: string; link?: string }[] = [];
|
||||
|
||||
// If no locations are passed in and the user has a daily api key then default to daily
|
||||
if (
|
||||
(typeof rest?.locations === "undefined" || rest.locations?.length === 0) &&
|
||||
typeof appKeys.api_key === "string"
|
||||
) {
|
||||
locations = [{ type: DailyLocationType }];
|
||||
}
|
||||
|
||||
// If its defaulting to daily no point handling compute as its done
|
||||
if (defaultConferencingData && defaultConferencingData.appSlug !== "daily-video") {
|
||||
const credentials = ctx.user.credentials;
|
||||
const foundApp = getApps(credentials).filter((app) => app.slug === defaultConferencingData.appSlug)[0]; // There is only one possible install here so index [0] is the one we are looking for ;
|
||||
const locationType = foundApp?.locationOption?.value ?? DailyLocationType; // Default to Daily if no location type is found
|
||||
locations = [{ type: locationType, link: defaultConferencingData.appLink }];
|
||||
}
|
||||
|
||||
const data: Prisma.EventTypeCreateInput = {
|
||||
...rest,
|
||||
owner: teamId ? undefined : { connect: { id: userId } },
|
||||
@@ -415,8 +439,7 @@ export const eventTypesRouter = router({
|
||||
id: userId,
|
||||
},
|
||||
},
|
||||
...((typeof rest?.locations === "undefined" || rest.locations?.length === 0) &&
|
||||
typeof appKeys.api_key === "string" && { locations: [{ type: DailyLocationType }] }),
|
||||
locations,
|
||||
};
|
||||
|
||||
if (teamId && schedulingType) {
|
||||
Reference in New Issue
Block a user