Merge pull request #3860 from Infisical/secret-sync-selection-improvements

improvement(secret-sync/app-connection): Add search/pagination to secret sync and app connection selection modals
This commit is contained in:
Scott Wilson
2025-06-26 14:50:44 -07:00
committed by GitHub
3 changed files with 248 additions and 143 deletions
@@ -58,6 +58,7 @@ export const CreateSecretSyncModal = ({ onOpenChange, selectSync = null, ...prop
} }
onPointerDownOutside={(e) => e.preventDefault()} onPointerDownOutside={(e) => e.preventDefault()}
className="max-w-2xl" className="max-w-2xl"
bodyClassName="overflow-visible"
subTitle={selectedSync ? undefined : "Select a third-party service to sync secrets to."} subTitle={selectedSync ? undefined : "Select a third-party service to sync secrets to."}
> >
<Content <Content
@@ -1,10 +1,11 @@
import { faWrench } from "@fortawesome/free-solid-svg-icons"; import { useMemo } from "react";
import { faInfoCircle, faMagnifyingGlass, faSearch } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Spinner, Tooltip } from "@app/components/v2"; import { EmptyState, Input, Pagination, Spinner, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context"; import { useSubscription } from "@app/context";
import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs"; import { SECRET_SYNC_MAP } from "@app/helpers/secretSyncs";
import { usePopUp } from "@app/hooks"; import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import { SecretSync, useSecretSyncOptions } from "@app/hooks/api/secretSyncs"; import { SecretSync, useSecretSyncOptions } from "@app/hooks/api/secretSyncs";
import { UpgradePlanModal } from "../license/UpgradePlanModal"; import { UpgradePlanModal } from "../license/UpgradePlanModal";
@@ -19,6 +20,26 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
const { search, setSearch, setPage, page, perPage, setPerPage, offset } = usePagination("", {
initPerPage: 16
});
const filteredOptions = useMemo(
() =>
secretSyncOptions?.filter(
({ name, destination }) =>
name?.toLowerCase().includes(search.trim().toLowerCase()) ||
destination.toLowerCase().includes(search.toLowerCase())
) ?? [],
[secretSyncOptions, search]
);
useResetPageHelper({
totalCount: filteredOptions.length,
offset,
setPage
});
if (isPending) { if (isPending) {
return ( return (
<div className="flex h-full flex-col items-center justify-center py-2.5"> <div className="flex h-full flex-col items-center justify-center py-2.5">
@@ -29,8 +50,16 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
} }
return ( return (
<div className="grid grid-cols-4 gap-2"> <div className="flex flex-col gap-4">
{secretSyncOptions?.map(({ destination, enterprise }) => { <Input
value={search}
onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search options..."
className="bg-mineshaft-800 placeholder:text-mineshaft-400"
/>
<div className="grid h-[29.5rem] grid-cols-4 content-start gap-2">
{filteredOptions.slice(offset, perPage * page)?.map(({ destination, enterprise }) => {
const { image, name } = SECRET_SYNC_MAP[destination]; const { image, name } = SECRET_SYNC_MAP[destination];
return ( return (
<button <button
@@ -55,11 +84,17 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
</button> </button>
); );
})} })}
<UpgradePlanModal {!filteredOptions?.length && (
isOpen={popUp.upgradePlan.isOpen} <EmptyState
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} className="col-span-full mt-40"
text="You can use every Secret Sync if you switch to Infisical's Enterprise plan." title="No Secret Syncs match search"
icon={faSearch}
/> />
)}
</div>
{Boolean(filteredOptions.length) && (
<Pagination
startAdornment={
<Tooltip <Tooltip
side="bottom" side="bottom"
className="max-w-sm py-4" className="max-w-sm py-4"
@@ -91,13 +126,27 @@ export const SecretSyncSelect = ({ onSelect }: Props) => {
</> </>
} }
> >
<div className="group relative flex h-28 flex-col items-center justify-center rounded-md border border-dashed border-mineshaft-600 bg-mineshaft-800 p-4 hover:bg-mineshaft-900/50"> <div className="-ml-3 flex items-center gap-1.5 text-mineshaft-400">
<FontAwesomeIcon className="mt-auto text-3xl" icon={faWrench} /> <span className="text-xs">
<div className="mt-auto max-w-xs text-center text-xs font-medium text-gray-300 duration-200 group-hover:text-gray-200"> Don&#39;t see the third-party service you&#39;re looking for?
Coming Soon </span>
</div> <FontAwesomeIcon size="xs" icon={faInfoCircle} />
</div> </div>
</Tooltip> </Tooltip>
}
count={filteredOptions.length}
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={setPerPage}
perPageList={[16]}
/>
)}
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use every Secret Sync if you switch to Infisical's Enterprise plan."
/>
</div> </div>
); );
}; };
@@ -1,11 +1,12 @@
import { faWrench } from "@fortawesome/free-solid-svg-icons"; import { useMemo } from "react";
import { faInfoCircle, faMagnifyingGlass, faSearch } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal"; import { UpgradePlanModal } from "@app/components/license/UpgradePlanModal";
import { Spinner, Tooltip } from "@app/components/v2"; import { EmptyState, Input, Pagination, Spinner, Tooltip } from "@app/components/v2";
import { useSubscription } from "@app/context"; import { useSubscription } from "@app/context";
import { APP_CONNECTION_MAP } from "@app/helpers/appConnections"; import { APP_CONNECTION_MAP } from "@app/helpers/appConnections";
import { usePopUp } from "@app/hooks"; import { usePagination, usePopUp, useResetPageHelper } from "@app/hooks";
import { useAppConnectionOptions } from "@app/hooks/api/appConnections"; import { useAppConnectionOptions } from "@app/hooks/api/appConnections";
import { AppConnection } from "@app/hooks/api/appConnections/enums"; import { AppConnection } from "@app/hooks/api/appConnections/enums";
@@ -19,6 +20,26 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => {
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const); const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
const { search, setSearch, setPage, page, perPage, setPerPage, offset } = usePagination("", {
initPerPage: 16
});
const filteredOptions = useMemo(
() =>
appConnectionOptions?.filter(
({ name, app }) =>
name?.toLowerCase().includes(search.trim().toLowerCase()) ||
app.toLowerCase().includes(search.toLowerCase())
) ?? [],
[appConnectionOptions, search]
);
useResetPageHelper({
totalCount: filteredOptions.length,
offset,
setPage
});
if (isPending) { if (isPending) {
return ( return (
<div className="flex h-full flex-col items-center justify-center py-2.5"> <div className="flex h-full flex-col items-center justify-center py-2.5">
@@ -29,9 +50,23 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => {
} }
return ( return (
<div className="grid grid-cols-4 gap-2"> <div className="flex flex-col gap-4">
{appConnectionOptions?.map((option) => { <Input
const { image, name, size = 50, enterprise = false, icon } = APP_CONNECTION_MAP[option.app]; value={search}
onChange={(e) => setSearch(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search options..."
className="bg-mineshaft-800 placeholder:text-mineshaft-400"
/>
<div className="grid h-[29.5rem] grid-cols-4 content-start gap-2">
{filteredOptions.slice(offset, perPage * page)?.map((option) => {
const {
image,
name,
size = 50,
enterprise = false,
icon
} = APP_CONNECTION_MAP[option.app];
return ( return (
<button <button
@@ -66,20 +101,26 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => {
</button> </button>
); );
})} })}
<UpgradePlanModal {!filteredOptions?.length && (
isOpen={popUp.upgradePlan.isOpen} <EmptyState
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)} className="col-span-full mt-40"
text="You can use every App Connection if you switch to Infisical's Enterprise plan." title="No App Connections match search"
icon={faSearch}
/> />
)}
</div>
{Boolean(filteredOptions.length) && (
<Pagination
startAdornment={
<Tooltip <Tooltip
side="bottom" side="bottom"
className="max-w-sm py-4" className="max-w-sm py-4"
content={ content={
<> <>
<p className="mb-2">Infisical is constantly adding support for more connections.</p> <p className="mb-2">Infisical is constantly adding support for more services.</p>
<p> <p>
{`If you don't see the third-party {`If you don't see the third-party
app you're looking for,`}{" "} service you're looking for,`}{" "}
<a <a
target="_blank" target="_blank"
className="underline hover:text-mineshaft-300" className="underline hover:text-mineshaft-300"
@@ -102,13 +143,27 @@ export const AppConnectionsSelect = ({ onSelect }: Props) => {
</> </>
} }
> >
<div className="group relative flex h-28 flex-col items-center justify-center rounded-md border border-dashed border-mineshaft-600 bg-mineshaft-800 p-4"> <div className="-ml-3 flex items-center gap-1.5 text-mineshaft-400">
<FontAwesomeIcon className="mt-auto text-xl" icon={faWrench} /> <span className="text-xs">
<div className="mt-auto max-w-xs text-center text-sm font-medium text-gray-300 duration-200 group-hover:text-gray-200"> Don&#39;t see the third-party service you&#39;re looking for?
Coming Soon </span>
</div> <FontAwesomeIcon size="xs" icon={faInfoCircle} />
</div> </div>
</Tooltip> </Tooltip>
}
count={filteredOptions.length}
page={page}
perPage={perPage}
onChangePage={setPage}
onChangePerPage={setPerPage}
perPageList={[16]}
/>
)}
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You can use every App Connection if you switch to Infisical's Enterprise plan."
/>
</div> </div>
); );
}; };