Files
calendar/packages/app-store/_components/AppCard.tsx
T
Benny JooGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
76bba1ca05 refactor: remove circular dependency between appstore and trpc (#23768)
* mv PaymentPage to web

* mv 2 more components to web

* refactor

* update imports

* mv

* revert

* fix

* fix

* update

* migrate relevant test too

* move setup components to web from appstore

* move wipemycalother

* mv

* fix typechecks

* mv more routing forms components

* fix

* fix

* fix

* fix: resolve type errors in SingleForm.tsx after migration

- Updated SingleFormComponentProps type to include enriched form properties
- Added proper typing for user, team, nonOrgUsername, nonOrgTeamslug, userOrigin, teamOrigin
- Fixes type errors that occurred after migrating SingleForm.tsx from packages/app-store/routing-forms/components/
- Resolves both SingleForm.tsx and TestForm.tsx type errors

Co-Authored-By: benny@cal.com <sldisek783@gmail.com>

* delete

* refactors

* fix

* fix

* fix

* remove usage of trpc client from OmniInstallAppButton.tsx

* fix test

* import only type

* fix test

* remove trpc package from appstore package.json

* remove remaining trpc usage

* update test

* fix type checks

* fix errors

* fix

* make it error

* nit

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2025-09-14 12:51:05 +00:00

150 lines
5.2 KiB
TypeScript

import { useAutoAnimate } from "@formkit/auto-animate/react";
import Link from "next/link";
import { useAppContextWithSchema } from "@calcom/app-store/EventTypeAppContext";
import { useIsPlatform } from "@calcom/atoms/hooks/useIsPlatform";
import { useLocale } from "@calcom/lib/hooks/useLocale";
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 { AppCardApp } from "../types";
import OmniInstallAppButton from "./OmniInstallAppButton";
export default function AppCard({
app,
description,
switchOnClick,
switchChecked,
children,
returnTo,
teamId,
disableSwitch,
switchTooltip,
hideSettingsIcon = false,
hideAppCardOptions = false,
onAppInstallSuccess,
}: {
app: AppCardApp;
onAppInstallSuccess: () => void;
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 } = useLocale();
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}
onAppInstallSuccess={onAppInstallSuccess}
/>
)}
</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>
);
}