Files
calendar/packages/app-store/_components/AppCard.tsx
T
073faf9e10 feat: Salesforce Event Type App v3 designs (#21100)
* section component

* more section UI

* wip

* core components styled

* all components using sections

* match icons to figma

* small inputs

* fix select small to be the same as small text

* center flex rows

* enforce inputs to be full spanning grid

* tidy up section container for single inputs

* fix select and i18n

* wip

* install available apps

* raw heading

* app install card

* fix app logos width x height

* fix gap

* match design for other button

* fix prop name

* push i18n string

* fix doc snippets

* fix docs

* chore: remove yarn.lock changes

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2025-05-06 08:21:20 +00:00

150 lines
5.3 KiB
TypeScript

import { useAutoAnimate } from "@formkit/auto-animate/react";
import Link from "next/link";
import { useTranslation } from "react-i18next";
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
import type { RouterOutputs } from "@calcom/trpc/react";
import classNames from "@calcom/ui/classNames";
import { Button } from "@calcom/ui/components/button";
import { Switch } from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";
import { Section } from "@calcom/ui/components/section";
import type { CredentialOwner } from "../types";
import OmniInstallAppButton from "./OmniInstallAppButton";
export default function AppCard({
app,
description,
switchOnClick,
switchChecked,
children,
returnTo,
teamId,
disableSwitch,
switchTooltip,
hideSettingsIcon = false,
hideAppCardOptions = false,
}: {
app: RouterOutputs["viewer"]["apps"]["integrations"]["items"][number] & {
credentialOwner?: CredentialOwner;
};
description?: React.ReactNode;
switchChecked?: boolean;
switchOnClick?: (e: boolean) => void;
children?: React.ReactNode;
returnTo?: string;
teamId?: number;
LockedIcon?: React.ReactNode;
disableSwitch?: boolean;
switchTooltip?: string;
hideSettingsIcon?: boolean;
hideAppCardOptions?: boolean;
}) {
const { t } = useTranslation();
const [animationRef] = useAutoAnimate<HTMLDivElement>();
const { setAppData, LockedIcon, disabled: managedDisabled } = useAppContextWithSchema();
const isPlatform = useIsPlatform();
return (
<Section className={classNames(!app?.isInstalled && "rounded-xl")}>
<Section.Header
rawHeading={
<div>
<div className="flex w-full items-center gap-1">
<Section.Title>{app?.name}</Section.Title>
{!app?.isInstalled && (
<span className="bg-emphasis ml-1 rounded px-1 py-0.5 text-xs font-medium leading-3 tracking-[0.01em]">
{app?.categories[0].charAt(0).toUpperCase() + app?.categories[0].slice(1)}
</span>
)}
</div>
<Section.Description>{app?.description}</Section.Description>
</div>
}
iconSlot={
<Link href={`/apps/${app.slug}`} className="flex h-8 w-8 items-center justify-center">
<img
className={classNames(
app?.logo.includes("-dark") && "dark:invert",
"max-h-full max-w-full object-contain"
)}
src={app?.logo}
alt={app?.name}
/>
</Link>
}>
<div>
<div>
{/* {app.credentialOwner && !isPlatform && (
<div className="ml-auto">
<Badge variant="gray">
<div className="flex items-center">
<Avatar
className="mr-2"
alt={app.credentialOwner.name || "Credential Owner Name"}
size="sm"
imageSrc={app.credentialOwner.avatar}
/>
{app.credentialOwner.name}
</div>
</Badge>
</div>
)} */}
{app?.isInstalled || app.credentialOwner ? (
<div className="ml-auto flex items-center">
<Switch
size="sm"
disabled={!app.enabled || managedDisabled || disableSwitch}
onCheckedChange={(enabled) => {
if (switchOnClick) {
switchOnClick(enabled);
}
setAppData("enabled", enabled);
}}
checked={switchChecked}
LockedIcon={LockedIcon}
data-testid={`${app.slug}-app-switch`}
tooltip={switchTooltip}
/>
</div>
) : (
<OmniInstallAppButton
className="ml-auto flex items-center"
appId={app.slug}
returnTo={returnTo}
teamId={teamId}
/>
)}
</div>
</div>
</Section.Header>
{hideAppCardOptions
? null
: app?.isInstalled &&
switchChecked && (
<div ref={animationRef}>
{app.isSetupAlready === undefined || app.isSetupAlready ? (
<div className="relative text-sm [&_input]:mb-0 [&_input]:leading-4">
{!hideSettingsIcon && !isPlatform && (
<Link href={`/apps/${app.slug}/setup`} className="absolute right-4 top-4">
<Icon name="settings" className="text-default h-4 w-4" aria-hidden="true" />
</Link>
)}
{children}
</div>
) : (
<div className="flex h-64 w-full flex-col items-center justify-center gap-4 ">
<p>{t("this_app_is_not_setup_already")}</p>
<Link href={`/apps/${app.slug}/setup`}>
<Button StartIcon="settings">{t("setup")}</Button>
</Link>
</div>
)}
</div>
)}
</Section>
);
}