// REFACTOR(akhilmhdh): This file needs to be split into multiple components too complex import { useEffect, useMemo, useState } from "react"; import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import Head from "next/head"; import Link from "next/link"; import { useRouter } from "next/router"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { faSlack } from "@fortawesome/free-brands-svg-icons"; import { faFolderOpen, faStar } from "@fortawesome/free-regular-svg-icons"; import { faArrowRight, faArrowUpRightFromSquare, faBorderAll, faCheck, faCheckCircle, faClipboard, faExclamationCircle, faFileShield, faHandPeace, faList, faMagnifyingGlass, faNetworkWired, faPlug, faPlus, faStar as faSolidStar, faUserPlus } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { yupResolver } from "@hookform/resolvers/yup"; import * as Tabs from "@radix-ui/react-tabs"; import * as yup from "yup"; import { createNotification } from "@app/components/notifications"; import { OrgPermissionCan } from "@app/components/permissions"; import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck"; import { Accordion, AccordionContent, AccordionItem, AccordionTrigger, Button, Checkbox, FormControl, IconButton, Input, Modal, ModalContent, Select, SelectItem, Skeleton, UpgradePlanModal } from "@app/components/v2"; import { OrgPermissionActions, OrgPermissionSubjects, useOrganization, useSubscription, useUser, useWorkspace } from "@app/context"; import { fetchOrgUsers, useAddUserToWsNonE2EE, useCreateWorkspace, useGetExternalKmsList, useRegisterUserAction } from "@app/hooks/api"; import { INTERNAL_KMS_KEY_ID } from "@app/hooks/api/kms/types"; // import { fetchUserWsKey } from "@app/hooks/api/keys/queries"; import { useFetchServerStatus } from "@app/hooks/api/serverDetails"; import { Workspace } from "@app/hooks/api/types"; import { useUpdateUserProjectFavorites } from "@app/hooks/api/users/mutation"; import { useGetUserProjectFavorites } from "@app/hooks/api/users/queries"; import { usePopUp } from "@app/hooks/usePopUp"; const features = [ { id: 0, name: "Kubernetes Operator", link: "https://infisical.com/docs/documentation/getting-started/kubernetes", description: "Pull secrets into your Kubernetes containers and automatically redeploy upon secret changes." }, { id: 1, name: "Infisical Agent", link: "https://infisical.com/docs/infisical-agent/overview", description: "Inject secrets into your apps without modifying any application logic." } ]; type ItemProps = { text: string; subText: string; complete: boolean; icon: IconProp; time: string; userAction?: string; link?: string; }; enum ProjectsViewMode { GRID = "grid", LIST = "list" } function copyToClipboard(id: string, setState: (value: boolean) => void) { // Get the text field const copyText = document.getElementById(id) as HTMLInputElement; // Select the text field copyText.select(); copyText.setSelectionRange(0, 99999); // For mobile devices // Copy the text inside the text field navigator.clipboard.writeText(copyText.value); setState(true); setTimeout(() => setState(false), 2000); // Alert the copied text // alert("Copied the text: " + copyText.value); } const CodeItem = ({ isCopied, setIsCopied, textExplanation, code, id }: { isCopied: boolean; setIsCopied: (value: boolean) => void; textExplanation: string; code: string; id: string; }) => { return ( <>

{textExplanation}

); }; const TabsObject = () => { const [downloadCodeCopied, setDownloadCodeCopied] = useState(false); const [downloadCode2Copied, setDownloadCode2Copied] = useState(false); const [loginCodeCopied, setLoginCodeCopied] = useState(false); const [initCodeCopied, setInitCodeCopied] = useState(false); const [runCodeCopied, setRunCodeCopied] = useState(false); return ( MacOS Windows {/* Arch Linux */} Other Platforms

You can find example of start commands for different frameworks{" "} here .{" "}

You can find example of start commands for different frameworks{" "} here .{" "}

); }; const LearningItem = ({ text, subText, complete, icon, time, userAction, link }: ItemProps): JSX.Element => { const registerUserAction = useRegisterUserAction(); if (link) { return (
null} role="button" tabIndex={0} onClick={async () => { if (userAction && userAction !== "first_time_secrets_pushed") { await registerUserAction.mutateAsync(userAction); } }} className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${ complete ? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700" : "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700" } text-mineshaft-100 duration-200`} >
{complete && (
)}
{text}
{subText}
{complete ? "Complete!" : `About ${time}`}
{/* {complete &&
} */}
); } return (
null} role="button" tabIndex={0} onClick={async () => { if (userAction) { await registerUserAction.mutateAsync(userAction); } }} className="relative my-1.5 flex h-[5.5rem] w-full cursor-pointer items-center justify-between overflow-hidden rounded-md border border-dashed border-bunker-400 bg-bunker-700 py-2 pl-2 pr-6 shadow-xl duration-200 hover:bg-bunker-500" >
{complete && (
)}
{text}
{subText}
{complete ? "Complete!" : `About ${time}`}
{complete &&
}
); }; const LearningItemSquare = ({ text, subText, complete, icon, time, userAction, link }: ItemProps): JSX.Element => { const registerUserAction = useRegisterUserAction(); return (
null} role="button" tabIndex={0} onClick={async () => { if (userAction && userAction !== "first_time_secrets_pushed") { await registerUserAction.mutateAsync(userAction); } }} className={`group relative flex w-full items-center justify-between overflow-hidden rounded-md border ${ complete ? "cursor-default border-mineshaft-900 bg-gradient-to-r from-[#0e1f01] to-mineshaft-700" : "cursor-pointer border-mineshaft-600 bg-mineshaft-800 shadow-xl hover:bg-mineshaft-700" } text-mineshaft-100 duration-200`} >
{complete && (
)}
{complete ? "Complete!" : `About ${time}`}
{text}
{subText}
); }; const formSchema = yup.object({ name: yup .string() .required() .label("Project Name") .trim() .max(64, "Too long, maximum length is 64 characters"), addMembers: yup.bool().required().label("Add Members"), kmsKeyId: yup.string().label("KMS Key ID") }); type TAddProjectFormData = yup.InferType; // #TODO: Update all the workspaceIds const OrganizationPage = () => { const { t } = useTranslation(); const router = useRouter(); const { workspaces, isLoading: isWorkspaceLoading } = useWorkspace(); const { currentOrg } = useOrganization(); const routerOrgId = String(router.query.id); const orgWorkspaces = workspaces?.filter((workspace) => workspace.orgId === routerOrgId) || []; const { data: projectFavorites, isLoading: isProjectFavoritesLoading } = useGetUserProjectFavorites(currentOrg?.id!); const { mutateAsync: updateUserProjectFavorites } = useUpdateUserProjectFavorites(); const isProjectViewLoading = isWorkspaceLoading || isProjectFavoritesLoading; const addUsersToProject = useAddUserToWsNonE2EE(); const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([ "addNewWs", "upgradePlan" ] as const); const { control, formState: { isSubmitting }, reset, handleSubmit } = useForm({ resolver: yupResolver(formSchema), defaultValues: { kmsKeyId: INTERNAL_KMS_KEY_ID } }); const [hasUserClickedSlack, setHasUserClickedSlack] = useState(false); const [hasUserClickedIntro, setHasUserClickedIntro] = useState(false); const [hasUserPushedSecrets, setHasUserPushedSecrets] = useState(false); const [usersInOrg, setUsersInOrg] = useState(false); const [searchFilter, setSearchFilter] = useState(""); const createWs = useCreateWorkspace(); const { user } = useUser(); const { data: serverDetails } = useFetchServerStatus(); const [projectsViewMode, setProjectsViewMode] = useState( (localStorage.getItem("projectsViewMode") as ProjectsViewMode) || ProjectsViewMode.GRID ); const { data: externalKmsList } = useGetExternalKmsList(currentOrg?.id!); const onCreateProject = async ({ name, addMembers, kmsKeyId }: TAddProjectFormData) => { // type check if (!currentOrg) return; if (!user) return; try { const { data: { project: { id: newProjectId } } } = await createWs.mutateAsync({ projectName: name, kmsKeyId: kmsKeyId !== INTERNAL_KMS_KEY_ID ? kmsKeyId : undefined }); if (addMembers) { const orgUsers = await fetchOrgUsers(currentOrg.id); await addUsersToProject.mutateAsync({ usernames: orgUsers .filter( (member) => member.user.username !== user.username && member.status === "accepted" ) .map((member) => member.user.username), projectId: newProjectId, orgId: currentOrg.id }); } // eslint-disable-next-line no-promise-executor-return -- We do this because the function returns too fast, which sometimes causes an error when the user is redirected. await new Promise((resolve) => setTimeout(resolve, 2_000)); handlePopUpClose("addNewWs"); createNotification({ text: "Project created", type: "success" }); router.push(`/project/${newProjectId}/secrets/overview`); } catch (err) { console.error(err); createNotification({ text: "Failed to create project", type: "error" }); } }; const { subscription } = useSubscription(); const isAddingProjectsAllowed = subscription?.workspaceLimit ? subscription.workspacesUsed < subscription.workspaceLimit : true; useEffect(() => { onboardingCheck({ orgId: routerOrgId, setHasUserClickedIntro, setHasUserClickedSlack, setHasUserPushedSecrets, setUsersInOrg }); }, []); const isWorkspaceEmpty = !isWorkspaceLoading && orgWorkspaces?.length === 0; const filteredWorkspaces = orgWorkspaces.filter((ws) => ws?.name?.toLowerCase().includes(searchFilter.toLowerCase()) ); const { workspacesWithFaveProp, favoriteWorkspaces, nonFavoriteWorkspaces } = useMemo(() => { const workspacesWithFav = filteredWorkspaces .map((w): Workspace & { isFavorite: boolean } => ({ ...w, isFavorite: Boolean(projectFavorites?.includes(w.id)) })) .sort((a, b) => Number(b.isFavorite) - Number(a.isFavorite)); const favWorkspaces = workspacesWithFav.filter((w) => w.isFavorite); const nonFavWorkspaces = workspacesWithFav.filter((w) => !w.isFavorite); return { workspacesWithFaveProp: workspacesWithFav, favoriteWorkspaces: favWorkspaces, nonFavoriteWorkspaces: nonFavWorkspaces }; }, [filteredWorkspaces, projectFavorites]); const addProjectToFavorites = async (projectId: string) => { try { if (currentOrg?.id) { await updateUserProjectFavorites({ orgId: currentOrg?.id, projectFavorites: [...(projectFavorites || []), projectId] }); } } catch (err) { createNotification({ text: "Failed to add project to favorites.", type: "error" }); } }; const removeProjectFromFavorites = async (projectId: string) => { try { if (currentOrg?.id) { await updateUserProjectFavorites({ orgId: currentOrg?.id, projectFavorites: [...(projectFavorites || []).filter((entry) => entry !== projectId)] }); } } catch (err) { createNotification({ text: "Failed to remove project from favorites.", type: "error" }); } }; const renderProjectGridItem = (workspace: Workspace, isFavorite: boolean) => ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
{ router.push(`/project/${workspace.id}/secrets/overview`); localStorage.setItem("projectData.id", workspace.id); }} key={workspace.id} className="min-w-72 flex h-40 cursor-pointer flex-col justify-between rounded-md border border-mineshaft-600 bg-mineshaft-800 p-4" >
{workspace.name}
{isFavorite ? ( { e.stopPropagation(); removeProjectFromFavorites(workspace.id); }} /> ) : ( { e.stopPropagation(); addProjectToFavorites(workspace.id); }} /> )}
{workspace.environments?.length || 0} environments
); const renderProjectListItem = (workspace: Workspace, isFavorite: boolean, index: number) => ( // eslint-disable-next-line jsx-a11y/no-static-element-interactions, jsx-a11y/click-events-have-key-events
{ router.push(`/project/${workspace.id}/secrets/overview`); localStorage.setItem("projectData.id", workspace.id); }} key={workspace.id} className={`min-w-72 group grid h-14 cursor-pointer grid-cols-6 border-t border-l border-r border-mineshaft-600 bg-mineshaft-800 px-6 hover:bg-mineshaft-700 ${ index === 0 && "rounded-t-md" } ${index === filteredWorkspaces.length - 1 && "rounded-b-md border-b"}`} >
{workspace.name}
{workspace.environments?.length || 0} environments
{isFavorite ? ( { e.stopPropagation(); removeProjectFromFavorites(workspace.id); }} /> ) : ( { e.stopPropagation(); addProjectToFavorites(workspace.id); }} /> )}
); const projectsGridView = ( <> {favoriteWorkspaces.length > 0 && ( <>

Favorites

0 && "border-b border-mineshaft-600" } py-4 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4`} > {favoriteWorkspaces.map((workspace) => renderProjectGridItem(workspace, true))}
)}
{isProjectViewLoading && Array.apply(0, Array(3)).map((_x, i) => (
))} {!isProjectViewLoading && nonFavoriteWorkspaces.map((workspace) => renderProjectGridItem(workspace, false))}
); const projectsListView = (
{isProjectViewLoading && Array.apply(0, Array(3)).map((_x, i) => (
))} {!isProjectViewLoading && workspacesWithFaveProp.map((workspace, ind) => renderProjectListItem(workspace, workspace.isFavorite, ind) )}
); return (
{t("common.head-title", { title: t("settings.members.title") })} {!serverDetails?.redisConfigured && (

Announcements

Attention: Updated versions of Infisical now require Redis for full functionality. Learn how to configure it here .
)}

Projects

setSearchFilter(e.target.value)} leftIcon={} />
{ localStorage.setItem("projectsViewMode", ProjectsViewMode.GRID); setProjectsViewMode(ProjectsViewMode.GRID); }} ariaLabel="grid" size="xs" className={`${ projectsViewMode === ProjectsViewMode.GRID ? "bg-mineshaft-500" : "bg-transparent" } min-w-[2.4rem] border-none hover:bg-mineshaft-600`} > { localStorage.setItem("projectsViewMode", ProjectsViewMode.LIST); setProjectsViewMode(ProjectsViewMode.LIST); }} ariaLabel="list" size="xs" className={`${ projectsViewMode === ProjectsViewMode.LIST ? "bg-mineshaft-500" : "bg-transparent" } min-w-[2.4rem] border-none hover:bg-mineshaft-600`} >
{(isAllowed) => ( )}
{projectsViewMode === ProjectsViewMode.LIST ? projectsListView : projectsGridView} {isWorkspaceEmpty && (
You are not part of any projects in this organization yet. When you are, they will appear here.
Create a new project, or ask other organization members to give you necessary permissions.
)}

Explore Infisical

{features.map((feature) => (
{feature.name}
{feature.description}

Setup time: 20 min

Learn more{" "}
))}
{!(new Date().getTime() - new Date(user?.createdAt).getTime() < 30 * 24 * 60 * 60 * 1000) && (

Onboarding Guide

{orgWorkspaces.length !== 0 && ( <> )}
{orgWorkspaces.length !== 0 && (
{false && (
)}
Inject secrets locally
Replace .env files with a more secure and efficient alternative.
About 2 min
{false &&
}
)} {orgWorkspaces.length !== 0 && ( )}
)} { handlePopUpToggle("addNewWs", isModalOpen); reset(); }} >
( )} />
( {(isAllowed) => (
Add all members of my organization to this project
)}
)} />
Advanced Settings
( )} control={control} name="kmsKeyId" />
handlePopUpToggle("upgradePlan", isOpen)} text="You have exceeded the number of projects allowed on the free plan." /> {/* */}
); }; Object.assign(OrganizationPage, { requireAuth: true }); export default OrganizationPage;