import { useAutoAnimate } from "@formkit/auto-animate/react"; import { useRouter } from "next/router"; import type { UIEvent } from "react"; import { useEffect, useRef, useState } from "react"; import { classNames } from "@calcom/lib"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { AppFrontendPayload as App } from "@calcom/types/App"; import type { CredentialFrontendPayload as Credential } from "@calcom/types/Credential"; import { EmptyScreen } from "../empty-screen"; import { FiChevronLeft, FiChevronRight, FiSearch } from "../icon"; import { AppCard } from "./AppCard"; export function useShouldShowArrows() { const ref = useRef(null); const [showArrowScroll, setShowArrowScroll] = useState({ left: false, right: false, }); useEffect(() => { const appCategoryList = ref.current; if (appCategoryList && appCategoryList.scrollWidth > appCategoryList.clientWidth) { setShowArrowScroll({ left: false, right: true }); } }, []); const calculateScroll = (e: UIEvent) => { setShowArrowScroll({ left: e.currentTarget.scrollLeft > 0, right: Math.floor(e.currentTarget.scrollWidth) - Math.floor(e.currentTarget.offsetWidth) !== Math.floor(e.currentTarget.scrollLeft), }); }; return { ref, calculateScroll, leftVisible: showArrowScroll.left, rightVisible: showArrowScroll.right }; } type AllAppsPropsType = { apps: (App & { credentials?: Credential[] })[]; searchText?: string; categories: string[]; }; interface CategoryTabProps { selectedCategory: string | null; categories: string[]; searchText?: string; } function CategoryTab({ selectedCategory, categories, searchText }: CategoryTabProps) { const { t } = useLocale(); const router = useRouter(); const { ref, calculateScroll, leftVisible, rightVisible } = useShouldShowArrows(); const handleLeft = () => { if (ref.current) { ref.current.scrollLeft -= 100; } }; const handleRight = () => { if (ref.current) { ref.current.scrollLeft += 100; } }; return (

{searchText ? t("search") : t("explore_apps", { category: (selectedCategory && selectedCategory[0].toUpperCase() + selectedCategory.slice(1)) || t("all_apps"), })}

{leftVisible && ( )}
    calculateScroll(e)} ref={ref}>
  • { router.replace(router.asPath.split("?")[0], undefined, { shallow: true }); }} className={classNames( selectedCategory === null ? "bg-gray-900 text-gray-50" : "bg-gray-50 text-gray-900", "rounded-md px-4 py-2.5 text-sm font-medium hover:cursor-pointer hover:bg-gray-900 hover:text-gray-50" )}> {t("all_apps")}
  • {categories.map((cat, pos) => (
  • { if (selectedCategory === cat) { router.replace(router.asPath.split("?")[0], undefined, { shallow: true }); } else { router.replace(router.asPath.split("?")[0] + `?category=${cat}`, undefined, { shallow: true, }); } }} className={classNames( selectedCategory === cat ? "bg-gray-900 text-gray-50" : "bg-gray-50 text-gray-900", "rounded-md px-4 py-2.5 text-sm font-medium hover:cursor-pointer hover:bg-gray-900 hover:text-gray-50" )}> {cat[0].toUpperCase() + cat.slice(1)}
  • ))}
{rightVisible && ( )}
); } export function AllApps({ apps, searchText, categories }: AllAppsPropsType) { const router = useRouter(); const { t } = useLocale(); const [selectedCategory, setSelectedCategory] = useState(null); const [appsContainerRef, enableAnimation] = useAutoAnimate(); if (searchText) { enableAnimation && enableAnimation(false); } useEffect(() => { const queryCategory = typeof router.query.category === "string" && categories.includes(router.query.category) ? router.query.category : null; setSelectedCategory(queryCategory); }, [router.query.category]); const filteredApps = apps .filter((app) => selectedCategory !== null ? app.categories ? app.categories.includes(selectedCategory) : app.category === selectedCategory : true ) .filter((app) => (searchText ? app.name.toLowerCase().includes(searchText.toLowerCase()) : true)) .sort(function (a, b) { if (a.name < b.name) return -1; else if (a.name > b.name) return 1; return 0; }); return (
{filteredApps.length ? (
{filteredApps.map((app) => ( ))}{" "}
) : ( )}
); }