diff --git a/frontend/src/components/v2/Menu/Menu.tsx b/frontend/src/components/v2/Menu/Menu.tsx index 632218020..87ef29b5c 100644 --- a/frontend/src/components/v2/Menu/Menu.tsx +++ b/frontend/src/components/v2/Menu/Menu.tsx @@ -48,7 +48,7 @@ export const MenuItem = <T extends ElementType = "button">({ > <li className={twMerge( - "group px-1 py-2.5 mt-0.5 font-inter flex flex-col text-sm text-bunker-100 transition-all rounded cursor-pointer hover:bg-mineshaft-700 duration-50", + "group px-1 py-2 mt-0.5 font-inter flex flex-col text-sm text-bunker-100 transition-all rounded cursor-pointer hover:bg-mineshaft-700 duration-50", isSelected && "bg-mineshaft-600 hover:bg-mineshaft-600", isDisabled && "hover:bg-transparent cursor-not-allowed", className @@ -56,16 +56,16 @@ export const MenuItem = <T extends ElementType = "button">({ > <motion.span className="w-full flex flex-row items-center justify-start rounded-sm"> <Item type="button" role="menuitem" className="flex items-center relative" ref={inputRef} {...props}> - <div className={`${isSelected ? "visisble" : "invisible"} absolute w-[0.25rem] rounded-md h-8 bg-primary`}/> + <div className={`${isSelected ? "visisble" : "invisible"} absolute w-[0.2rem] rounded-md h-7 bg-primary`}/> {/* {icon && <span className="mr-3 ml-4 w-5 block group-hover:hidden">{icon}</span>} */} <Lottie lottieRef={iconRef} - style={{ width: 24, height: 24 }} + style={{ width: 22, height: 22 }} // eslint-disable-next-line import/no-dynamic-require animationData={require(`../../../../public/lotties/${icon}.json`)} loop={false} autoplay={false} - className="my-auto ml-3 mr-3" + className="my-auto ml-[0.1rem] mr-3" /> <span className="flex-grow text-left">{children}</span> </Item> diff --git a/frontend/src/components/v2/Tabs/Tabs.tsx b/frontend/src/components/v2/Tabs/Tabs.tsx index e29b5e6a9..cc7e755f6 100644 --- a/frontend/src/components/v2/Tabs/Tabs.tsx +++ b/frontend/src/components/v2/Tabs/Tabs.tsx @@ -72,12 +72,12 @@ export const TabsObject = () => { > Windows </Tabs.Trigger> - <Tabs.Trigger + {/* <Tabs.Trigger className="bg-bunker-700 px-5 h-10 flex-1 flex items-center justify-center text-sm leading-none text-bunker-300 select-none first:rounded-tl-md last:rounded-tr-md data-[state=active]:text-primary data-[state=active]:font-medium data-[state=active]:focus:relative data-[state=active]:border-b data-[state=active]:border-primary outline-none cursor-default" value="tab3" > Arch Linux - </Tabs.Trigger> + </Tabs.Trigger> */} <a target='_blank' rel="noopener noreferrer" @@ -88,7 +88,7 @@ export const TabsObject = () => { </a> </Tabs.List> <Tabs.Content - className="grow p-5 bg-bunker-700 rounded-b-md outline-none cursor-default" + className="grow p-5 pt-0 bg-bunker-700 rounded-b-md outline-none cursor-default" value="tab1" > <CodeItem isCopied={downloadCodeCopied} setIsCopied={setDownloadCodeCopied} textExplanation="1. Download CLI" code="brew install infisical/get-cli/infisical" id="downloadCode" /> @@ -105,10 +105,10 @@ export const TabsObject = () => { </a>. </p> </Tabs.Content> <Tabs.Content - className="grow p-5 bg-bunker-700 rounded-b-md outline-none" + className="grow p-5 pt-0 bg-bunker-700 rounded-b-md outline-none" value="tab2" > - <CodeItem isCopied={downloadCodeCopied} setIsCopied={setDownloadCodeCopied} textExplanation="1. Download CLI" code="brew install infisical/get-cli/infisical" id="downloadCodeW" /> + <CodeItem isCopied={downloadCodeCopied} setIsCopied={setDownloadCodeCopied} textExplanation="1. Download CLI" code="scoop bucket add org https://github.com/Infisical/scoop-infisical.git" id="downloadCodeW" /> <div className='font-mono text-sm px-3 py-2 mt-2 bg-bunker rounded-md border border-mineshaft-600 flex flex-row items-center justify-between'> <input disabled value="scoop install infisical" id="downloadCodeW2" className='w-full bg-transparent text-bunker-200'/> <button @@ -138,22 +138,5 @@ export const TabsObject = () => { here </a>. </p> </Tabs.Content> - <Tabs.Content - className="grow p-5 bg-bunker-700 rounded-b-md outline-none cursor-default" - value="tab3" - > - <CodeItem isCopied={downloadCodeCopied} setIsCopied={setDownloadCodeCopied} textExplanation="1. Download CLI" code="brew install infisical/get-cli/infisical" id="downloadCodeL" /> - <CodeItem isCopied={loginCodeCopied} setIsCopied={setLoginCodeCopied} textExplanation="2. Login" code="infisical login" id="loginCodeL" /> - <CodeItem isCopied={initCodeCopied} setIsCopied={setInitCodeCopied} textExplanation="3. Choose Project" code="infisical init" id="initCodeL" /> - <CodeItem isCopied={runCodeCopied} setIsCopied={setRunCodeCopied} textExplanation="4. Done! Now, you can prepend your usual start script with:" code="infisical run -- [YOUR USUAL CODE START SCRIPT GOES HERE]" id="runCodeL" /> - <p className='text-bunker-300 text-sm mt-2'>You can find example of start commands for different frameworks <a - className='text-primary underline underline-offset-2' - target="_blank" - rel="noopener noreferrer" - href='https://infisical.com/docs/integrations/overview' - > - here - </a>. </p> - </Tabs.Content> </Tabs.Root> }; \ No newline at end of file diff --git a/frontend/src/layouts/AppLayout/AppLayout.tsx b/frontend/src/layouts/AppLayout/AppLayout.tsx index c4f858bfe..50422864f 100644 --- a/frontend/src/layouts/AppLayout/AppLayout.tsx +++ b/frontend/src/layouts/AppLayout/AppLayout.tsx @@ -12,7 +12,7 @@ import { Controller, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; import Link from "next/link"; import { useRouter } from "next/router"; -import { faBookOpen, faMobile, faPlus } from "@fortawesome/free-solid-svg-icons"; +import { faAngleDown, faBookOpen, faMobile, faPlus, faQuestion } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { yupResolver } from "@hookform/resolvers/yup"; import queryString from "query-string"; @@ -257,13 +257,20 @@ export const AppLayout = ({ children }: LayoutProps) => { return ( <> <div className="dark hidden h-screen w-full flex-col overflow-x-hidden md:flex"> - <Navbar /> + {/* <Navbar /> */} <div className="flex flex-grow flex-col overflow-y-hidden md:flex-row"> <aside className="w-full border-r border-mineshaft-600 bg-gradient-to-tr from-mineshaft-700 via-mineshaft-800 to-mineshaft-900 md:w-60"> <nav className="items-between flex h-full flex-col justify-between"> <div> - {currentWorkspace && router.asPath !== "/noprojects" ? ( - <div className="mt-3 mb-4 w-full p-4"> + <div className="h-12 px-3 flex items-center pt-6 cursor-default"> + <div className="mr-auto flex items-center hover:bg-mineshaft-600 py-1.5 pl-1.5 pr-2 rounded-md"> + <div className="w-5 h-5 rounded-md bg-[#E0ED34] flex justify-center items-center">I</div> + <div className="pl-3.5 text-mineshaft-100 text-sm">Infisical <FontAwesomeIcon icon={faAngleDown} className="text-xs pl-1 pt-1 text-mineshaft-300" /></div> + </div> + <div className="w-5 h-5 rounded-full bg-green hover:opacity-80 pr-1"></div> + </div> + {!router.asPath.includes("org") && (currentWorkspace && router.asPath !== "/noprojects" ? ( + <div className="mt-3 mb-4 w-full p-3"> <p className="ml-1.5 mb-1 text-xs font-semibold uppercase text-gray-400"> Project </p> @@ -331,9 +338,9 @@ export const AppLayout = ({ children }: LayoutProps) => { Add Project </Button> </div> - )} - <div className={`${currentWorkspace && router.asPath !== "/noprojects" ? "block" : "hidden"}`}> - <Menu> + ))} + <div className={`px-1 ${currentWorkspace && router.asPath !== "/noprojects" ? "block" : "hidden"}`}> + {router.asPath.includes("project") ? <Menu> <Link href={`/dashboard/${currentWorkspace?._id}`} passHref> <a> <MenuItem @@ -386,53 +393,51 @@ export const AppLayout = ({ children }: LayoutProps) => { </a> </Link> </Menu> + : <Menu className="mt-4"> + <Link href={`/dashboard/${currentWorkspace?._id}`} passHref> + <a> + <MenuItem + isSelected={router.asPath.includes(`/dashboard/${currentWorkspace?._id}`)} + icon="system-outline-90-lock-closed" + > + Overview + </MenuItem> + </a> + </Link> + <Link href={`/users/${currentWorkspace?._id}`} passHref> + <a> + <MenuItem + isSelected={router.asPath === `/users/${currentWorkspace?._id}`} + icon="system-outline-96-groups" + > + {t("nav.menu.members")} + </MenuItem> + </a> + </Link> + <Link href={`/settings/project/${currentWorkspace?._id}`} passHref> + <a> + <MenuItem + isSelected={ + router.asPath === `/settings/project/${currentWorkspace?._id}` + } + icon="system-outline-109-slider-toggle-settings" + > + Org Setting + </MenuItem> + </a> + </Link> + </Menu>} </div> </div> - <div className="mt-40 mb-4 w-full px-2"> - {router.asPath.split("/")[1] === "home" ? ( - <div className="relative flex cursor-pointer rounded bg-primary-50/10 px-0.5 py-2.5 text-sm text-white"> - <div className="absolute inset-0 top-0 my-1 ml-1 mr-1 w-1 rounded-xl bg-primary" /> - <p className="ml-4 mr-2 flex w-6 items-center justify-center text-lg"> - <FontAwesomeIcon icon={faBookOpen} /> - </p> - Infisical Guide - <img - src={`/images/progress-${totalOnboardingActionsDone === 0 ? "0" : ""}${ - totalOnboardingActionsDone === 1 ? "14" : "" - }${totalOnboardingActionsDone === 2 ? "28" : ""}${ - totalOnboardingActionsDone === 3 ? "43" : "" - }${totalOnboardingActionsDone === 4 ? "57" : ""}${ - totalOnboardingActionsDone === 5 ? "71" : "" - }.svg`} - height={58} - width={58} - alt="progress bar" - className="absolute right-2 -top-2" - /> - </div> - ) : ( - <Link href={`/home/${currentWorkspace?._id}`}> - <div className="mt-max relative flex h-10 cursor-pointer overflow-visible rounded bg-white/10 p-2.5 text-sm text-white hover:bg-primary-50/[0.15]"> - <p className="flex w-10 items-center justify-center text-lg"> - <FontAwesomeIcon icon={faBookOpen} /> - </p> - Infisical Guide - <img - src={`/images/progress-${totalOnboardingActionsDone === 0 ? "0" : ""}${ - totalOnboardingActionsDone === 1 ? "14" : "" - }${totalOnboardingActionsDone === 2 ? "28" : ""}${ - totalOnboardingActionsDone === 3 ? "43" : "" - }${totalOnboardingActionsDone === 4 ? "57" : ""}${ - totalOnboardingActionsDone === 5 ? "71" : "" - }.svg`} - height={58} - width={58} - alt="progress bar" - className="absolute right-2 -top-2" - /> - </div> - </Link> - )} + <div className="mt-40 mb-4 w-full px-2 text-mineshaft-400 cursor-default pl-6 text-sm"> + <div className="hover:text-mineshaft-200 duration-200 mb-3"> + <FontAwesomeIcon icon={faPlus} className="mr-3"/> + Invite people + </div> + <div className="hover:text-mineshaft-200 duration-200 mb-2"> + <FontAwesomeIcon icon={faQuestion} className="px-[0.1rem] mr-3"/> + Help & Support + </div> </div> </nav> </aside> diff --git a/frontend/src/pages/org/[id].tsx b/frontend/src/pages/org/[id].tsx new file mode 100644 index 000000000..34029b370 --- /dev/null +++ b/frontend/src/pages/org/[id].tsx @@ -0,0 +1,433 @@ +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import Head from "next/head"; +import Image from "next/image"; +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 { faArrowRight, faCheckCircle, faHandPeace, faMagnifyingGlass, faNetworkWired, faPlug, faPlus, faStar, faUserPlus } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; + +import AddProjectMemberDialog from "@app/components/basic/dialog/AddProjectMemberDialog"; +import ProjectUsersTable from "@app/components/basic/table/ProjectUsersTable"; +import guidGenerator from "@app/components/utilities/randomId"; +import { useWorkspace } from "@app/context"; +import { Workspace } from "@app/hooks/api/workspace/types"; + +import onboardingCheck from "~/components/utilities/checks/OnboardingCheck"; +import { TabsObject } from "~/components/v2/Tabs"; + +import { + decryptAssymmetric, + encryptAssymmetric +} from "../../components/utilities/cryptography/crypto"; +import getOrganizationUsers from "../api/organization/GetOrgUsers"; +import getUser from "../api/user/getUser"; +import registerUserAction from "../api/userActions/registerUserAction"; +// import DeleteUserDialog from '@app/components/basic/dialog/DeleteUserDialog'; +import addUserToWorkspace from "../api/workspace/addUserToWorkspace"; +import getWorkspaceUsers from "../api/workspace/getWorkspaceUsers"; +import uploadKeys from "../api/workspace/uploadKeys"; + +interface UserProps { + firstName: string; + lastName: string; + email: string; + _id: string; + publicKey: string; +} + +interface MembershipProps { + deniedPermissions: any[]; + user: UserProps; + inviteEmail: string; + role: string; + status: string; + _id: string; +} + +const features = [{ + "_id": 0, + "name": "Kubernetes Operator", + "description": "Pull secrets into your Kubernetes containers and automatically redeploy upon secret changes." +}] + +type ItemProps = { + text: string; + subText: string; + complete: boolean; + icon: IconProp; + time: string; + userAction?: string; + link?: string; +}; + +const learningItem = ({ + text, + subText, + complete, + icon, + time, + userAction, + link +}: ItemProps): JSX.Element => { + if (link) { + return ( + <a + target={`${link.includes("https") ? "_blank" : "_self"}`} + rel="noopener noreferrer" + className={`w-full ${complete && "opacity-30 duration-200 hover:opacity-100"}`} + href={link} + > + <div className={`${complete ? "bg-gradient-to-r from-primary-500/70 p-[0.07rem]" : ""} mb-3 rounded-md`}> + <div + onKeyDown={() => null} + role="button" + tabIndex={0} + onClick={async () => { + if (userAction && userAction !== "first_time_secrets_pushed") { + await registerUserAction({ + action: userAction + }); + } + }} + className={`group relative flex h-[5.5rem] w-full items-center justify-between overflow-hidden rounded-md border ${complete? "bg-gradient-to-r from-[#0e1f01] to-mineshaft-700 border-mineshaft-900 cursor-default" : "bg-mineshaft-800 hover:bg-mineshaft-700 border-mineshaft-600 shadow-xl cursor-pointer"} duration-200 text-mineshaft-100`} + > + <div className="mr-4 flex flex-row items-center"> + <FontAwesomeIcon icon={icon} className="mx-2 w-16 text-4xl" /> + {complete && ( + <div className="absolute left-12 top-10 flex h-7 w-7 items-center justify-center rounded-full bg-bunker-500 p-2 group-hover:bg-mineshaft-700"> + <FontAwesomeIcon icon={faCheckCircle} className="h-5 w-5 text-4xl text-primary" /> + </div> + )} + <div className="flex flex-col items-start"> + <div className="mt-0.5 text-xl font-semibold">{text}</div> + <div className="text-sm font-normal">{subText}</div> + </div> + </div> + <div + className={`w-32 pr-8 text-right text-sm font-semibold ${complete && "text-primary"}`} + > + {complete ? "Complete!" : `About ${time}`} + </div> + {/* {complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-primary" />} */} + </div> + </div> + </a> + ); + } + return ( + <div + onKeyDown={() => null} + role="button" + tabIndex={0} + onClick={async () => { + if (userAction) { + await registerUserAction({ + action: 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" + > + <div className="mr-4 flex flex-row items-center"> + <FontAwesomeIcon icon={icon} className="mx-2 w-16 text-4xl" /> + {complete && ( + <div className="absolute left-11 top-10 h-7 w-7 rounded-full bg-bunker-700"> + <FontAwesomeIcon + icon={faCheckCircle} + className="absolute left-12 top-16 h-5 w-5 text-4xl text-primary" + /> + </div> + )} + <div className="flex flex-col items-start"> + <div className="mt-0.5 text-xl font-semibold">{text}</div> + <div className="mt-0.5 text-sm font-normal">{subText}</div> + </div> + </div> + <div className={`w-28 pr-4 text-right text-sm font-semibold ${complete && "text-primary"}`}> + {complete ? "Complete!" : `About ${time}`} + </div> + {complete && <div className="absolute bottom-0 left-0 h-1 w-full bg-primary" />} + </div> + ); +}; + +// #TODO: Update all the workspaceIds + +export default function Organization() { + const [isAddOpen, setIsAddOpen] = useState(false); + // let [isDeleteOpen, setIsDeleteOpen] = useState(false); + // let [userIdToBeDeleted, setUserIdToBeDeleted] = useState(false); + const [email, setEmail] = useState(""); + const [personalEmail, setPersonalEmail] = useState(""); + const [searchUsers, setSearchUsers] = useState(""); + + const { t } = useTranslation(); + + const router = useRouter(); + const workspaceId = router.query.id as string; + + const [userList, setUserList] = useState<any[]>([]); + const [isUserListLoading, setIsUserListLoading] = useState(true); + const [orgUserList, setOrgUserList] = useState<any[]>([]); + const { workspaces, isLoading: isWorkspaceLoading } = useWorkspace(); + + useEffect(() => { + (async () => { + const user = await getUser(); + setPersonalEmail(user.email); + + // This part quiries the current users of a project + const workspaceUsers = await getWorkspaceUsers({ + workspaceId + }); + const tempUserList = workspaceUsers.map((membership: MembershipProps) => ({ + key: guidGenerator(), + firstName: membership.user?.firstName, + lastName: membership.user?.lastName, + email: membership.user?.email === null ? membership.inviteEmail : membership.user?.email, + role: membership?.role, + status: membership?.status, + userId: membership.user?._id, + membershipId: membership._id, + deniedPermissions: membership.deniedPermissions, + publicKey: membership.user?.publicKey + })); + setUserList(tempUserList); + + setIsUserListLoading(false); + + // This is needed to know wha users from an org (if any), we are able to add to a certain project + const orgUsers = await getOrganizationUsers({ + orgId: String(localStorage.getItem("orgData.id")) + }); + setOrgUserList(orgUsers); + setEmail( + orgUsers + ?.filter((membership: MembershipProps) => membership.status === "accepted") + .map((membership: MembershipProps) => membership.user.email) + .filter( + (usEmail: string) => + !tempUserList?.map((user1: UserProps) => user1.email).includes(usEmail) + )[0] + ); + })(); + }, []); + + const closeAddModal = () => { + setIsAddOpen(false); + }; + + const openAddModal = () => { + setIsAddOpen(true); + }; + + // function closeDeleteModal() { + // setIsDeleteOpen(false); + // } + + // function deleteMembership(userId) { + // deleteUserFromWorkspace(userId, router.query.id) + // } + + // function openDeleteModal() { + // setIsDeleteOpen(true); + // } + + const submitAddModal = async () => { + const result = await addUserToWorkspace(email, workspaceId); + if (result?.invitee && result?.latestKey) { + const PRIVATE_KEY = localStorage.getItem("PRIVATE_KEY") as string; + + // assymmetrically decrypt symmetric key with local private key + const key = decryptAssymmetric({ + ciphertext: result.latestKey.encryptedKey, + nonce: result.latestKey.nonce, + publicKey: result.latestKey.sender.publicKey, + privateKey: PRIVATE_KEY + }); + + const { ciphertext, nonce } = encryptAssymmetric({ + plaintext: key, + publicKey: result.invitee.publicKey, + privateKey: PRIVATE_KEY + }); + + uploadKeys(workspaceId, result.invitee._id, ciphertext, nonce); + } + setEmail(""); + setIsAddOpen(false); + router.rel + oad(); + }; + const [hasUserClickedSlack, setHasUserClickedSlack] = useState(false); + const [hasUserClickedIntro, setHasUserClickedIntro] = useState(false); + const [hasUserStarred, setHasUserStarred] = useState(false); + const [hasUserPushedSecrets, setHasUserPushedSecrets] = useState(false); + const [usersInOrg, setUsersInOrg] = useState(false); + + useEffect(() => { + onboardingCheck({ + setHasUserClickedIntro, + setHasUserClickedSlack, + setHasUserPushedSecrets, + setHasUserStarred, + setUsersInOrg + }); + }, []); + + return userList ? ( + <div className="flex max-w-7xl mx-auto flex-col justify-start bg-bunker-800 md:h-screen"> + <Head> + <title>{t("common.head-title", { title: t("settings.members.title") })}</title> + <link rel="icon" href="/infisical.ico" /> + </Head> + <div className="flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl mb-4"> + <p className="mr-4 font-semibold text-white">Projects</p> + <div className="mt-4 w-full grid grid-flow-dense gap-4" style={{ gridTemplateColumns: "repeat(auto-fill, minmax(256px, 4fr))" }}> + {workspaces.map(workspace => <div key={workspace._id} className="h-40 w-72 rounded-md bg-mineshaft-800 border border-mineshaft-600 p-4 flex flex-col justify-between"> + <div className="text-lg text-mineshaft-100 mt-0">{workspace.name}</div> + <div className="text-sm text-mineshaft-300 mt-0 pb-6">{(workspace.environments?.length || 0)} environments</div> + <Link href="/dashbaord"> + <div className="group cursor-default ml-auto hover:bg-primary-800/20 text-sm text-mineshaft-300 hover:text-mineshaft-200 bg-mineshaft-900 py-2 px-4 rounded-full w-max border border-mineshaft-600 hover:border-primary-500/80">Explore <FontAwesomeIcon icon={faArrowRight} className="pl-1.5 pr-0.5 group-hover:pl-2 group-hover:pr-0 duration-200" /></div> + </Link> + </div>)} + </div> + </div> + <div className="flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl mb-4"> + <p className="mr-4 font-semibold text-white mb-4">Onboarding Guide</p> + {learningItem({ + text: "Watch a video about Infisical", + subText: "", + complete: hasUserClickedIntro, + icon: faHandPeace, + time: "3 min", + userAction: "intro_cta_clicked", + link: "https://www.youtube.com/watch?v=PK23097-25I" + })} + {learningItem({ + text: "Add your secrets", + subText: "Click to see example secrets, and add your own.", + complete: hasUserPushedSecrets, + icon: faPlus, + time: "1 min", + userAction: "first_time_secrets_pushed", + link: `/dashboard/${router.query.id}` + })} + <div className="group text-mineshaft-100 relative mb-3 flex h-full w-full cursor-default flex-col items-center justify-between overflow-hidden rounded-md border border-mineshaft-600 bg-bunker-500 pl-2 pr-2 pt-4 pb-2 shadow-xl duration-200"> + <div className="mb-4 flex w-full flex-row items-center pr-4"> + <div className="mr-4 flex w-full flex-row items-center"> + <FontAwesomeIcon icon={faNetworkWired} className="mx-2 w-16 text-4xl" /> + {false && ( + <div className="absolute left-12 top-10 flex h-7 w-7 items-center justify-center rounded-full bg-bunker-500 p-2 group-hover:bg-mineshaft-700"> + <FontAwesomeIcon icon={faCheckCircle} className="h-5 w-5 text-4xl text-green" /> + </div> + )} + <div className="flex flex-col items-start pl-0.5"> + <div className="mt-0.5 text-xl font-semibold">Inject secrets locally</div> + <div className="text-sm font-normal"> + Replace .env files with a more secure and efficient alternative. + </div> + </div> + </div> + <div className={`w-28 pr-4 text-right text-sm font-semibold ${false && "text-green"}`}> + About 2 min + </div> + </div> + <TabsObject /> + {false && <div className="absolute bottom-0 left-0 h-1 w-full bg-green" />} + </div> + {learningItem({ + text: "Integrate Infisical with your infrastructure", + subText: "Connect Infisical to various 3rd party services and platforms.", + complete: false, + icon: faPlug, + time: "15 min", + link: "https://infisical.com/docs/integrations/overview" + })} + {learningItem({ + text: "Invite your teammates", + subText: "", + complete: usersInOrg, + icon: faUserPlus, + time: "2 min", + link: `/settings/org/${router.query.id}?invite` + })} + {learningItem({ + text: "Join Infisical Slack", + subText: "Have any questions? Ask us!", + complete: hasUserClickedSlack, + icon: faSlack, + time: "1 min", + userAction: "slack_cta_clicked", + link: "https://join.slack.com/t/infisical-users/shared_invite/zt-1wehzfnzn-1aMo5JcGENJiNAC2SD8Jlg" + })} + {/* <div className="mt-4 w-full grid grid-flow-dense gap-4" style={{ gridTemplateColumns: "repeat(auto-fill, minmax(256px, 4fr))" }}> + {workspaces.map(workspace => <div key={workspace._id} className="h-40 w-72 rounded-md bg-mineshaft-800 border border-mineshaft-600 p-4 flex flex-col justify-between"> + <div className="text-lg text-mineshaft-100 mt-0">{workspace.name}</div> + <Link href="/dashbaord"> + <div className="group cursor-default hover:bg-primary-800/20 text-sm text-mineshaft-300 hover:text-mineshaft-200 bg-mineshaft-900 py-2 px-4 rounded-full w-max border border-mineshaft-600 hover:border-primary-500/80">Explore <FontAwesomeIcon icon={faArrowRight} className="pl-1.5 pr-0.5 group-hover:pl-2 group-hover:pr-0 duration-200" /></div> + </Link> + </div>)} + </div> */} + </div> + <div className="flex flex-col items-start justify-start px-6 py-6 pb-0 text-3xl mb-4 pb-6"> + <p className="mr-4 font-semibold text-white">Explore More</p> + <div className="mt-4 w-full grid grid-flow-dense gap-4" style={{ gridTemplateColumns: "repeat(auto-fill, minmax(256px, 4fr))" }}> + {features.map(feature => <div key={feature._id} className="h-44 w-96 rounded-md bg-mineshaft-800 border border-mineshaft-600 p-4 flex flex-col justify-between"> + <div className="text-lg text-mineshaft-100 mt-0">{feature.name}</div> + <div className="text-[15px] font-light text-mineshaft-300 mb-4 mt-2">{feature.description}</div> + <div className="w-full flex items-center"> + <div className="text-mineshaft-300 text-[15px] font-light">Setup time: 20 min</div> + <Link href="/dashbaord"> + <div className="group cursor-default ml-auto hover:bg-primary-800/20 text-sm text-mineshaft-300 hover:text-mineshaft-200 bg-mineshaft-900 py-2 px-4 rounded-full w-max border border-mineshaft-600 hover:border-primary-500/80">Learn more <FontAwesomeIcon icon={faArrowRight} className="pl-1.5 pr-0.5 group-hover:pl-2 group-hover:pr-0 duration-200" /></div> + </Link> + </div> + </div>)} + </div> + </div> + <AddProjectMemberDialog + isOpen={isAddOpen} + closeModal={closeAddModal} + submitModal={submitAddModal} + email={email} + data={orgUserList + ?.filter((membership: MembershipProps) => membership.status === "accepted") + .map((membership: MembershipProps) => membership.user.email) + .filter( + (orgEmail) => !userList?.map((user1: UserProps) => user1.email).includes(orgEmail) + )} + setEmail={setEmail} + /> + {/* <DeleteUserDialog isOpen={isDeleteOpen} closeModal={closeDeleteModal} submitModal={deleteMembership} userIdToBeDeleted={userIdToBeDeleted}/> */} + {/* <div className="absolute right-4 top-36 flex w-full flex-row items-start px-6 pb-1"> + <div className="flex w-full max-w-sm flex flex-row ml-auto"> + <Input + className="h-[2.3rem] bg-mineshaft-800 placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80" + placeholder="Search by users..." + value={searchUsers} + onChange={(e) => setSearchUsers(e.target.value)} + leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />} + /> + </div> + <div className="ml-2 flex min-w-max flex-row items-start justify-start"> + <Button + text={String(t("section.members.add-member"))} + onButtonPressed={openAddModal} + color="mineshaft" + size="md" + icon={faPlus} + /> + </div> + </div> */} + </div> + ) : ( + <div className="relative z-10 mr-auto ml-2 flex h-full w-10/12 flex-col items-center justify-center bg-bunker-800"> + <Image src="/images/loading/loading.gif" height={70} width={120} alt="loading animation" /> + </div> + ); +} + +Organization.requireAuth = true; diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js index e8bd3b56b..29811c15a 100644 --- a/frontend/tailwind.config.js +++ b/frontend/tailwind.config.js @@ -1245,17 +1245,17 @@ module.exports = { colors: { // You can generate your own colors with this tool: https://javisperez.github.io/tailwindcolorshades/ primary: { - 50: '#fcfdf7', - 100: '#f8fcee', - 200: '#eef6d5', - 300: '#e3f1bc', - 400: '#cfe78a', - 500: '#badc58', - 600: '#a7c64f', - 700: '#8ca542', - 800: '#708435', - 900: '#5b6c2b', - DEFAULT: '#badc58' + 50: '#fffff5', + 100: '#fcfce8', + 200: '#f8faca', + 300: '#f4f7ab', + 400: '#ecf26d', + 500: '#e0ed34', + 600: '#c2d62b', + 700: '#97b31d', + 800: '#708f13', + 900: '#4d6b0b', + DEFAULT: '#e0ed34' }, grey: '#0d1117', mineshaft: { @@ -1339,7 +1339,31 @@ module.exports = { 800: '#1c7a44', 900: '#176437', DEFAULT: '#2ecc71' - } + }, + blue: { + 50: '#f2f8ff', + 100: '#e6f1ff', + 200: '#bfdbff', + 300: '#99c5ff', + 400: '#4d9aff', + 500: '#006eff', + 600: '#0063e6', + 700: '#0053bf', + 800: '#004299', + 900: '#00367d' + }, + darkblue: { + 50: '#f2f4f7', + 100: '#e6e8f0', + 200: '#bfc6d9', + 300: '#99a3c3', + 400: '#4d5f95', + 500: '#001a68', + 600: '#00175e', + 700: '#00144e', + 800: '#00103e', + 900: '#000d33' + }, }, keyframes: { type: {