+ {!router.asPath.includes("org") && (currentWorkspace && router.asPath !== "/noprojects" ? (
+
- {router.asPath.split("/")[1] === "home" ? (
-
-
-
-
-
- Infisical Guide
-

-
- ) : (
-
-
-
-
-
- Infisical Guide
-

-
-
- )}
+
+
+
+ Invite people
+
+
+
+ Help & Support
+
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 (
+
+
+
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`}
+ >
+
+
+ {complete && (
+
+
+
+ )}
+
+
+
+ {complete ? "Complete!" : `About ${time}`}
+
+ {/* {complete &&
} */}
+
+
+
+ );
+ }
+ return (
+
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"
+ >
+
+
+ {complete && (
+
+
+
+ )}
+
+
+
+ {complete ? "Complete!" : `About ${time}`}
+
+ {complete &&
}
+
+ );
+};
+
+// #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
([]);
+ const [isUserListLoading, setIsUserListLoading] = useState(true);
+ const [orgUserList, setOrgUserList] = useState([]);
+ 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 ? (
+
+
+
{t("common.head-title", { title: t("settings.members.title") })}
+
+
+
+
Projects
+
+ {workspaces.map(workspace =>
+
{workspace.name}
+
{(workspace.environments?.length || 0)} environments
+
+
Explore
+
+
)}
+
+
+
+
Onboarding Guide
+ {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}`
+ })}
+
+
+
+
+ {false && (
+
+
+
+ )}
+
+
Inject secrets locally
+
+ Replace .env files with a more secure and efficient alternative.
+
+
+
+
+ About 2 min
+
+
+
+ {false &&
}
+
+ {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"
+ })}
+ {/*
+ {workspaces.map(workspace =>
+
{workspace.name}
+
+
Explore
+
+
)}
+
*/}
+
+
+
Explore More
+
+ {features.map(feature =>
+
{feature.name}
+
{feature.description}
+
+
Setup time: 20 min
+
+
Learn more
+
+
+
)}
+
+
+
membership.status === "accepted")
+ .map((membership: MembershipProps) => membership.user.email)
+ .filter(
+ (orgEmail) => !userList?.map((user1: UserProps) => user1.email).includes(orgEmail)
+ )}
+ setEmail={setEmail}
+ />
+ {/* */}
+ {/*
+
+ setSearchUsers(e.target.value)}
+ leftIcon={}
+ />
+
+
+
+
+
*/}
+
+ ) : (
+
+
+
+ );
+}
+
+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: {