Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: new App install flow #11975

Merged
merged 36 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2585d62
feat(appStore): add isOAuth config
ThyMinimalDev Oct 20, 2023
550a1fe
refactor(appStore): EventTypeAppSettngsInterface
ThyMinimalDev Oct 20, 2023
ca0edf1
refactor(qr_code): split EventTypeAppCardInterface and EventTypeAppSe…
ThyMinimalDev Oct 20, 2023
a4d20e7
feat(appStore): redirect to onboarding for stripe and basecam
ThyMinimalDev Oct 20, 2023
905bc23
fix(ui/ScrollableArea): overflow indicator not working
ThyMinimalDev Oct 20, 2023
4e4a8b6
feat(apps): new install app flow
ThyMinimalDev Oct 20, 2023
4fba8af
fix(configureStep): get disabled props for settings
ThyMinimalDev Oct 20, 2023
d5b34b8
fix: getAppInstallsBySlug now use teamIds
ThyMinimalDev Oct 20, 2023
81347d9
chore: Alby to AppSettingsInterface
sean-brydon Jan 23, 2024
935745f
chore: Giphy to appsettings interface
sean-brydon Jan 23, 2024
7a401ee
chore: GTM migration
sean-brydon Jan 23, 2024
c01ca48
chore: GT4 - migration
sean-brydon Jan 23, 2024
c7b3bc8
chore: fathom migration
sean-brydon Jan 23, 2024
8703354
chore: paypal
sean-brydon Jan 23, 2024
f220f78
feat: basecamp migration
sean-brydon Jan 28, 2024
e4cac4a
feat: metapixel migration
sean-brydon Jan 28, 2024
a687aa9
feat:plausable
sean-brydon Jan 28, 2024
7e38f86
feat:stripe
sean-brydon Jan 28, 2024
c1be5a1
Merge branch 'main' into new-app-install-flow
ThyMinimalDev Jan 31, 2024
f443494
fix merge issues
ThyMinimalDev Jan 31, 2024
b117710
fix merge issues in new app install flow steps
ThyMinimalDev Jan 31, 2024
5dbecb7
wip: callback to onboarding
sean-brydon Feb 9, 2024
9b3fd28
fix: imports
sean-brydon Feb 9, 2024
25d4709
fix: more imports
sean-brydon Feb 9, 2024
dca6096
Merge branch 'main' into new-app-install-flow
keithwillcode Feb 9, 2024
718e1f7
feat: use redirect to onboarding on install flow
sean-brydon Feb 10, 2024
ab8ad87
Merge remote-tracking branch 'refs/remotes/origin/new-app-install-flo…
sean-brydon Feb 10, 2024
a6a97fa
Merge branch 'main' into new-app-install-flow
keithwillcode Feb 13, 2024
fad6c70
Merge branch 'main' into new-app-install-flow
joeauyeung Mar 22, 2024
200ff6c
Merge branch 'main' into new-app-install-flow
SomayChauhan Mar 30, 2024
7377de7
Merge branch 'main' into new-app-install-flow
SomayChauhan Apr 3, 2024
b6807ce
feat: New app install flow 2 (#14077)
SomayChauhan Apr 8, 2024
e72bfbb
Merge branch 'main' into new-app-install-flow
SomayChauhan Apr 10, 2024
eb3b10b
fix: type errors
SomayChauhan Apr 10, 2024
0fb6c8c
fix: failing tests
SomayChauhan Apr 10, 2024
954d8cc
Merge branch 'main' into new-app-install-flow
joeauyeung Apr 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions apps/api/v2/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
},
"exclude": ["./dist", "next-i18next.config.js"],
"include": ["./**/*.ts", "../../../packages/types/*.d.ts"]
"exclude": ["./dist"],
"include": ["./**/*.ts", "../../../packages/types/*.d.ts", "next-i18next.config.js"]
}
4 changes: 4 additions & 0 deletions apps/web/components/apps/AppPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export type AppPageProps = {
isGlobal?: AppType["isGlobal"];
logo: string;
slug: string;
dirName: string | undefined;
variant: string;
body: React.ReactNode;
categories: string[];
Expand Down Expand Up @@ -68,6 +69,7 @@ export const AppPage = ({
dependencies,
concurrentMeetings,
paid,
dirName,
}: AppPageProps) => {
const { t, i18n } = useLocale();
const hasDescriptionItems = descriptionItems && descriptionItems.length > 0;
Expand Down Expand Up @@ -223,6 +225,7 @@ export const AppPage = ({
multiInstall
concurrentMeetings={concurrentMeetings}
paid={paid}
dirName={dirName}
{...props}
/>
);
Expand Down Expand Up @@ -262,6 +265,7 @@ export const AppPage = ({
credentials={appDbQuery.data?.credentials}
concurrentMeetings={concurrentMeetings}
paid={paid}
dirName={dirName}
{...props}
/>
);
Expand Down
38 changes: 38 additions & 0 deletions apps/web/components/apps/InstallAppButtonChild.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import { useRouter } from "next/navigation";
import { useMemo } from "react";

import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import { appStoreMetadata } from "@calcom/app-store/appStoreMetaData";
import { doesAppSupportTeamInstall } from "@calcom/app-store/utils";
import { Spinner } from "@calcom/features/calendars/weeklyview/components/spinner/Spinner";
import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams";
import { AppOnboardingSteps } from "@calcom/lib/apps/appOnboardingSteps";
import { getAppOnboardingUrl } from "@calcom/lib/apps/getAppOnboardingUrl";
import { shouldRedirectToAppOnboarding } from "@calcom/lib/apps/shouldRedirectToAppOnboarding";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
Expand All @@ -26,7 +33,9 @@ export const InstallAppButtonChild = ({
multiInstall,
credentials,
concurrentMeetings,
dirName,
paid,
onClick,
...props
}: {
userAdminTeams?: UserAdminTeams;
Expand All @@ -36,8 +45,10 @@ export const InstallAppButtonChild = ({
credentials?: RouterOutputs["viewer"]["appCredentialsByType"]["credentials"];
concurrentMeetings?: boolean;
paid?: AppFrontendPayload["paid"];
dirName: string | undefined;
} & ButtonProps) => {
const { t } = useLocale();
const router = useRouter();

const mutation = useAddAppMutation(null, {
onSuccess: (data) => {
Expand All @@ -49,13 +60,26 @@ export const InstallAppButtonChild = ({
},
});
const shouldDisableInstallation = !multiInstall ? !!(credentials && credentials.length) : false;
const appMetadata = appStoreMetadata[dirName as keyof typeof appStoreMetadata];
const redirectToAppOnboarding = useMemo(() => shouldRedirectToAppOnboarding(appMetadata), [appMetadata]);

const _onClick = (e: React.MouseEvent<HTMLElement, MouseEvent>) => {
if (redirectToAppOnboarding) {
router.push(
getAppOnboardingUrl({ slug: addAppMutationInput.slug, step: AppOnboardingSteps.ACCOUNTS_STEP })
);
} else if (onClick) {
onClick(e);
}
};

// Paid apps don't support team installs at the moment
// Also, cal.ai(the only paid app at the moment) doesn't support team install either
if (paid) {
return (
<Button
data-testid="install-app-button"
onClick={_onClick}
{...props}
disabled={shouldDisableInstallation}
color="primary"
Expand All @@ -72,6 +96,7 @@ export const InstallAppButtonChild = ({
return (
<Button
data-testid="install-app-button"
onClick={_onClick}
{...props}
// @TODO: Overriding color and size prevent us from
// having to duplicate InstallAppButton for now.
Expand All @@ -83,6 +108,19 @@ export const InstallAppButtonChild = ({
);
}

if (redirectToAppOnboarding) {
return (
<Button
data-testid="install-app-button"
disabled={shouldDisableInstallation}
onClick={_onClick}
color="primary"
size="base"
{...props}>
{multiInstall ? t("install_another") : t("install_app")}
</Button>
);
}
return (
<Dropdown>
<DropdownMenuTrigger asChild>
Expand Down
97 changes: 97 additions & 0 deletions apps/web/components/apps/installation/AccountsStepCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import type { FC } from "react";
import React, { useState } from "react";

import { classNames } from "@calcom/lib";
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { Team, User } from "@calcom/prisma/client";
import { Avatar, StepCard } from "@calcom/ui";

type AccountSelectorProps = {
avatar?: string;
name: string;
alreadyInstalled: boolean;
onClick?: () => void;
loading: boolean;
testId: string;
};

const AccountSelector: FC<AccountSelectorProps> = ({
avatar,
alreadyInstalled,
name,
onClick,
loading,
testId,
}) => {
const { t } = useLocale();
const [selected, setSelected] = useState(false);
return (
<div
className={classNames(
"hover:bg-muted flex cursor-pointer flex-row items-center gap-2 p-1",
(alreadyInstalled || loading) && "cursor-not-allowed",
selected && "bg-muted animate-pulse"
)}
data-testid={testId}
onClick={() => {
if (onClick) {
setSelected(true);
onClick();
}
}}>
<Avatar
alt={avatar || ""}
imageSrc={avatar || `${CAL_URL}/${avatar}`} // if no image, use default avatar
size="sm"
/>
<div className="text-md pt-0.5 font-medium text-gray-500">
{name}
{alreadyInstalled ? <span className="ml-1 text-sm text-gray-400">{t("already_installed")}</span> : ""}
</div>
</div>
);
};

export type PersonalAccountProps = Pick<User, "id" | "avatar" | "name"> & { alreadyInstalled: boolean };

export type TeamsProp = (Pick<Team, "id" | "name" | "logo"> & {
alreadyInstalled: boolean;
})[];

type AccountStepCardProps = {
teams: TeamsProp;
personalAccount: PersonalAccountProps;
onSelect: (id?: number) => void;
loading: boolean;
};

export const AccountsStepCard: FC<AccountStepCardProps> = ({ teams, personalAccount, onSelect, loading }) => {
const { t } = useLocale();
return (
<StepCard>
<div className="text-sm font-medium text-gray-400">{t("install_app_on")}</div>
<div className={classNames("mt-2 flex flex-col gap-2 ")}>
<AccountSelector
testId="install-app-on-personal-account"
avatar={personalAccount.avatar ?? ""}
name={personalAccount.name ?? ""}
alreadyInstalled={personalAccount.alreadyInstalled}
onClick={() => !personalAccount.alreadyInstalled && !loading && onSelect()}
loading={loading}
/>
{teams.map((team) => (
<AccountSelector
key={team.id}
testId={`install-app-on-team-${team.id}`}
alreadyInstalled={team.alreadyInstalled}
avatar={team.logo ?? ""}
name={team.name}
onClick={() => !team.alreadyInstalled && !loading && onSelect(team.id)}
loading={loading}
/>
))}
</div>
</StepCard>
);
};