Files
calendar/packages/features/apps/AdminAppsList.tsx
T
+9 a9af2fb255 Admin Wizard Choose License (#6574)
* Implementation

* i18n

* More i18n

* extracted i18n, needs api to get most recent price, added hint: update later

* Fixing i18n var

* Fix booking filters not working for admin (#6576)

* fix: react-select overflow issue in some modals. (#6587)

* feat: add a disable overflow prop

* feat: use the disable overflow prop

* Tailwind Merge (#6596)

* Tailwind Merge

* Fix merge classNames

* [CAL-808] /availability/single - UI issue on buttons beside time inputs (#6561)

* [CAL-808] /availability/single - UI issue on buttons beside time inputs

* Update apps/web/public/static/locales/en/common.json

* Update packages/features/schedules/components/Schedule.tsx

* create new translation for tooltip

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>

* Bye bye submodules (#6585)

* WIP

* Uses ssh instead

* Update .gitignore

* Update .gitignore

* Update Makefile

* Update git-setup.sh

* Update git-setup.sh

* Replaced Makefile with bash script

* Update package.json

* fix: show button on empty string (#6601)

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* fix: add delete in dropdown (#6599)

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* Update README.md

* Update README.md

* Changed a neutral- classes to gray (#6603)

* Changed a neutral- classes to gray

* Changed all border-1 to border

* Update package.json

* Test fixes

* Yarn lock fixes

* Fix string equality check in git-setup.sh

* [CAL-811] Avatar icon not redirecting user back to the main page (#6586)

* Remove cursor-pointer, remove old Avatar* files

* Fixed styling for checkedSelect + some cleanup

Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>

* Harsh/add member invite (#6598)

Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: root <harsh.singh@gocomet.com>

* Regenerated lockfile without upgrade (#6610)

* fix: remove annoying outline when <Button /> clicked (#6537)

* fix: remove annoying outline when <Button /> clicked

* Delete yarn.lock

* remove 1 on 1 icon (#6609)

* removed 1-on-1 badge

* changed user to users for group events

* fix: case-sensitivity in apps path (#6552)

* fix: lowercase slug

* fix: make fallback blocking

* Fix FAB (#6611)

* feat: add LocationSelect component (#6571)

* feat: add LocationSelect component

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* fix: type error

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* chore: type error

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* Update booking filters design  (#6543)

* Update booking filters

* Add filter on YOUR bookings

* Fix pending members showing up in list

* Reduce the avatar size to 'sm' for now

* Bugfix/dropdown menu trigger as child remove class names (#6614)

* Fix UsernameTextfield to take right height

* Remove className side-effect

* Incorrect resolution version fixed

* Converted mobile DropdownMenuTrigger styles into Button

* v2.5.3

* fix: use items-center (#6618)

* fix tooltip and modal stacking issues (#6491)

* fix tooltip and modal stacking issues

* use z-index in larger screens and less

Co-authored-by: Alex van Andel <me@alexvanandel.com>

* Temporary fix (#6626)

* Fix Ga4 tracking (#6630)

* generic <UpgradeScreen> component (#6594)

* first attempt of <UpgradeScreen>

* changes to icons

* reverted changes back to initial state, needs fix: teams not showing

* WIP

* Fix weird reactnode error

* Fix loading text

* added upgradeTip to routing forms

* icon colors

* create and use hook to check if user has team plan

* use useTeamPlan for upgradeTeamsBadge

* replace huge svg with compressed jpeg

* responsive fixes

* Update packages/ui/components/badge/UpgradeTeamsBadge.tsx

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>

* Give team plan features to E2E tests

* Allow option to make a user part of team int ests

* Remove flash of paywall for team user

* Add team user for typeform tests as well

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>

* Removing env var to rely on db

* Restoring i18n keys, set loading moved

* Fixing tailwind-preset glob

* Wizard width fix for md+ screens

* Converting licenses options to radix radio

* Applying feedback + other tweaks

* Reverting this, not this PR related

* Unneeded code removal

* Reverting unneeded style change

* Applying feedback

* Removing licenseType

* Upgrades typescript

* Update yarn lock

* Typings

* Hotfix: ping,riverside,whereby and around not showing up in list (#6712)

* Hotfix: ping,riverside,whereby and around not showing up in list (#6712) (#6713)

* Adds deployment settings to DB (#6706)

* WIP

* Adds DeploymentTheme

* Add missing migrations

* Adds client extensions for deployment

* Cleanup

* Delete migration.sql

* Relying on both, env var and new model

* Restoring env example doc for backward compat

* Maximum call stack size exceeded fix?

* Revert upgrade

* Update index.ts

* Delete index.ts

* Not exposing license key, fixed radio behavior

* Covering undefined env var

* Self contained checkLicense

* Feedback

* Moar feedback

* Feedback

* Feedback

* Feedback

* Cleanup

---------

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: Nafees Nazik <84864519+G3root@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
Co-authored-by: gitstart-calcom <gitstart@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Harsh Singh <51085015+harshsinghatz@users.noreply.github.com>
Co-authored-by: Guest <guest@pop-os.localdomain>
Co-authored-by: root <harsh.singh@gocomet.com>
Co-authored-by: Luis Cadillo <luiscaf3r@gmail.com>
Co-authored-by: Mohammed Cherfaoui <hi@cherfaoui.dev>
Co-authored-by: Hariom Balhara <hariombalhara@gmail.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
2023-02-07 17:23:42 -07:00

351 lines
9.3 KiB
TypeScript

import { zodResolver } from "@hookform/resolvers/zod";
import { AppCategories } from "@prisma/client";
import { noop } from "lodash";
import { useRouter } from "next/router";
import { FC, useReducer, useState } from "react";
import { Controller, useForm } from "react-hook-form";
import { z } from "zod";
import AppCategoryNavigation from "@calcom/app-store/_components/AppCategoryNavigation";
import { appKeysSchemas } from "@calcom/app-store/apps.keys-schemas.generated";
import { classNames as cs } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { RouterOutputs, trpc } from "@calcom/trpc/react";
import {
Button,
ConfirmationDialogContent,
Dialog,
DialogClose,
DialogContent,
DialogFooter,
Dropdown,
DropdownItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
EmptyScreen,
Form,
List,
showToast,
SkeletonButton,
SkeletonContainer,
SkeletonText,
TextField,
} from "@calcom/ui";
import {
FiAlertCircle,
FiCheckCircle,
FiEdit,
FiMoreHorizontal,
FiXCircle,
} from "@calcom/ui/components/icon";
import AppListCard from "../../../apps/web/components/AppListCard";
type App = RouterOutputs["viewer"]["appsRouter"]["listLocal"][number];
const IntegrationContainer = ({
app,
category,
handleModelOpen,
}: {
app: App;
category: string;
handleModelOpen: (data: EditModalState) => void;
}) => {
const { t } = useLocale();
const utils = trpc.useContext();
const [disableDialog, setDisableDialog] = useState(false);
const showKeyModal = () => {
if (app.keys) {
handleModelOpen({
dirName: app.dirName,
keys: app.keys,
slug: app.slug,
type: app.type,
isOpen: "editKeys",
});
}
};
const enableAppMutation = trpc.viewer.appsRouter.toggle.useMutation({
onSuccess: (enabled) => {
utils.viewer.appsRouter.listLocal.invalidate({ category });
setDisableDialog(false);
showToast(
enabled ? t("app_is_enabled", { appName: app.name }) : t("app_is_disabled", { appName: app.name }),
"success"
);
if (enabled) {
showKeyModal();
}
},
onError: (error) => {
showToast(error.message, "error");
},
});
return (
<li>
<AppListCard
logo={app.logo}
description={app.description}
title={app.name}
isTemplate={app.isTemplate}
actions={
<div className="flex justify-self-end">
<Dropdown modal={false}>
<DropdownMenuTrigger asChild>
<Button StartIcon={FiMoreHorizontal} variant="icon" color="secondary" />
</DropdownMenuTrigger>
<DropdownMenuContent>
{app.keys && (
<DropdownMenuItem>
<DropdownItem onClick={showKeyModal} type="button" StartIcon={FiEdit}>
{t("edit_keys")}
</DropdownItem>
</DropdownMenuItem>
)}
<DropdownMenuItem
onClick={() => {
if (app.enabled) {
setDisableDialog(true);
} else {
enableAppMutation.mutate({ slug: app.slug, enabled: app.enabled });
}
}}>
<DropdownItem StartIcon={app.enabled ? FiXCircle : FiCheckCircle} type="button">
{app.enabled ? t("disable") : t("enable")}
</DropdownItem>
</DropdownMenuItem>
</DropdownMenuContent>
</Dropdown>
</div>
}
/>
<Dialog open={disableDialog} onOpenChange={setDisableDialog}>
<ConfirmationDialogContent
title={t("disable_app")}
variety="danger"
onConfirm={() => {
enableAppMutation.mutate({ slug: app.slug, enabled: app.enabled });
}}>
{t("disable_app_description")}
</ConfirmationDialogContent>
</Dialog>
</li>
);
};
const querySchema = z.object({
category: z
.nativeEnum({ ...AppCategories, conferencing: "conferencing" })
.optional()
.default(AppCategories.calendar),
});
const AdminAppsList = ({
baseURL,
className,
useQueryParam = false,
classNames,
onSubmit = noop,
...rest
}: {
baseURL: string;
classNames?: {
form?: string;
appCategoryNavigationRoot?: string;
appCategoryNavigationContainer?: string;
verticalTabsItem?: string;
};
className?: string;
useQueryParam?: boolean;
onSubmit?: () => void;
} & Omit<JSX.IntrinsicElements["form"], "onSubmit">) => {
return (
<form
{...rest}
className={
classNames?.form ?? "max-w-80 mb-4 rounded-md bg-white px-0 pt-0 md:max-w-full md:px-8 md:pt-10"
}
onSubmit={(e) => {
e.preventDefault();
onSubmit();
}}>
<AppCategoryNavigation
baseURL={baseURL}
useQueryParam={useQueryParam}
classNames={{
root: className,
verticalTabsItem: classNames?.verticalTabsItem,
container: cs("min-w-0 w-full", classNames?.appCategoryNavigationContainer ?? "max-w-[500px]"),
}}>
<AdminAppsListContainer />
</AppCategoryNavigation>
</form>
);
};
const EditKeysModal: FC<{
dirName: string;
slug: string;
type: string;
isOpen: boolean;
keys: App["keys"];
handleModelClose: () => void;
}> = (props) => {
const { t } = useLocale();
const { dirName, slug, type, isOpen, keys, handleModelClose } = props;
const appKeySchema = appKeysSchemas[dirName as keyof typeof appKeysSchemas];
const formMethods = useForm({
resolver: zodResolver(appKeySchema),
});
const saveKeysMutation = trpc.viewer.appsRouter.saveKeys.useMutation({
onSuccess: () => {
showToast(t("keys_have_been_saved"), "success");
handleModelClose();
},
onError: (error) => {
showToast(error.message, "error");
},
});
return (
<Dialog open={isOpen} onOpenChange={handleModelClose}>
<DialogContent title={t("edit_keys")} type="creation">
{!!keys && typeof keys === "object" && (
<Form
id="edit-keys"
form={formMethods}
handleSubmit={(values) =>
saveKeysMutation.mutate({
slug,
type,
keys: values,
dirName,
})
}
className="px-4 pb-4">
{Object.keys(keys).map((key) => (
<Controller
name={key}
key={key}
control={formMethods.control}
defaultValue={keys && keys[key] ? keys?.[key] : ""}
render={({ field: { value } }) => (
<TextField
label={key}
key={key}
name={key}
value={value}
onChange={(e) => {
formMethods.setValue(key, e?.target.value);
}}
/>
)}
/>
))}
</Form>
)}
<DialogFooter>
<DialogClose onClick={handleModelClose} />
<Button form="edit-keys" type="submit">
{t("save")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
};
interface EditModalState extends Pick<App, "keys"> {
isOpen: "none" | "editKeys" | "disableKeys";
dirName: string;
type: string;
slug: string;
}
const AdminAppsListContainer = () => {
const { t } = useLocale();
const router = useRouter();
const { category } = querySchema.parse(router.query);
const { data: apps, isLoading } = trpc.viewer.appsRouter.listLocal.useQuery(
{ category },
{ enabled: router.isReady }
);
const [modalState, setModalState] = useReducer(
(data: EditModalState, partialData: Partial<EditModalState>) => ({ ...data, ...partialData }),
{
keys: null,
isOpen: "none",
dirName: "",
type: "",
slug: "",
}
);
const handleModelClose = () =>
setModalState({ keys: null, isOpen: "none", dirName: "", slug: "", type: "" });
const handleModelOpen = (data: EditModalState) => setModalState({ ...data });
if (isLoading) return <SkeletonLoader />;
if (!apps) {
return (
<EmptyScreen
Icon={FiAlertCircle}
headline={t("no_available_apps")}
description={t("no_available_apps_description")}
/>
);
}
return (
<>
<List>
{apps.map((app) => (
<IntegrationContainer
handleModelOpen={handleModelOpen}
app={app}
key={app.name}
category={category}
/>
))}
</List>
<EditKeysModal
keys={modalState.keys}
dirName={modalState.dirName}
handleModelClose={handleModelClose}
isOpen={modalState.isOpen === "editKeys"}
slug={modalState.slug}
type={modalState.type}
/>
</>
);
};
export default AdminAppsList;
const SkeletonLoader = () => {
return (
<SkeletonContainer className="w-[30rem] pr-10">
<div className="mt-6 mb-8 space-y-6">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
</div>
</SkeletonContainer>
);
};