second iteration of the new sidebar

This commit is contained in:
Vladyslav Matsiiako
2023-07-09 23:58:27 -07:00
parent ec26404b94
commit bdf4ebd1bc
75 changed files with 1927 additions and 1698 deletions
+4 -5
View File
@@ -21,11 +21,10 @@ export default function Custom404() {
</a>{" "}
and we`ll fix it!{" "}
</p>
<Link
href='/dashboard'
className='bg-mineshaft-500 mt-8 py-2 px-4 rounded-md hover:bg-primary diration-200 hover:text-black cursor-pointer font-semibold'
>
Go to Dashboard
<Link href='/dashboard'>
<div className="mt-8 bg-mineshaft-500 py-2 px-4 rounded-md hover:bg-primary diration-200 hover:text-black font-semibold cursor-default">
Go to Dashboard
</div>
</Link>
<Image
src='/images/dragon-404.svg'
+7 -7
View File
@@ -1,7 +1,7 @@
import { useEffect } from "react";
import { useRouter } from "next/router";
import getWorkspaces from "./api/workspace/getWorkspaces";
import getOrganizations from "./api/organization/getOrgs";
export default function DashboardRedirect() {
const router = useRouter();
@@ -12,14 +12,14 @@ export default function DashboardRedirect() {
// eslint-disable-next-line react-hooks/exhaustive-deps
useEffect(() => {
(async () => {
let userWorkspace;
let userOrg;
try {
if (localStorage.getItem("projectData.id")) {
router.push(`/dashboard/${ localStorage.getItem("projectData.id")}`);
if (localStorage.getItem("orgData.id")) {
router.push(`/org/${localStorage.getItem("orgData.id")}/overview`);
} else {
const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0]._id;
router.push(`/dashboard/${ userWorkspace}`);
const userOrgs = await getOrganizations();
userOrg = userOrgs[0]._id;
router.push(`/org/${userOrg}/overview`);
}
} catch (error) {
console.log("Error - Not logged in yet");
-244
View File
@@ -1,244 +0,0 @@
import { useEffect, useState } from "react";
import Head from "next/head";
import Image from "next/image";
import { useRouter } from "next/router";
import { IconProp } from "@fortawesome/fontawesome-svg-core";
import { faSlack } from "@fortawesome/free-brands-svg-icons";
import {
faCheckCircle,
faHandPeace,
faNetworkWired,
faPlug,
faPlus,
faStar,
faUserPlus
} from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck";
import { TabsObject } from "@app/components/v2/Tabs";
import registerUserAction from "../api/userActions/registerUserAction";
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
onKeyDown={() => null}
role="button"
tabIndex={0}
onClick={async () => {
if (userAction && userAction !== "first_time_secrets_pushed") {
await registerUserAction({
action: userAction
});
}
}}
className="group relative mb-3 flex h-[5.5rem] w-full cursor-pointer items-center justify-between overflow-hidden rounded-md border border-mineshaft-600 bg-bunker-500 py-2 pl-2 pr-6 shadow-xl duration-200 hover:bg-mineshaft-700"
>
<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-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>
</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>
);
};
/**
* This tab is called Home because in the future it will include some company news,
* updates, roadmap, relavant blogs, etc. Currently it only has the setup instruction
* for the new users
*/
export default function Home() {
const router = useRouter();
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 (
<div className="mx-6 w-full pt-4 lg:mx-0">
<Head>
<title>Infisical Guide</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<div className="relative mx-auto flex max-w-3xl flex-col items-center px-6 py-6 text-lg text-gray-300 lg:max-w-4xl xl:max-w-5xl">
<div className="mt-12 w-full text-left text-5xl font-bold">Your quick start guide</div>
<div className="mb-14 w-full pt-2 pb-4 text-left text-lg text-bunker-300">
Click on the items below and follow the instructions.
</div>
<div className="absolute top-0 right-0 hidden h-min lg:block">
<Image
src="/images/dragon-book.svg"
height={250}
width={400}
alt="start guise dragon illustration"
/>
</div>
{learningItem({
text: "Get to know 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: "2 min",
userAction: "first_time_secrets_pushed",
link: `/dashboard/${router.query.id}`
})}
<div className="group 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"
})}
{learningItem({
text: "Star Infisical on GitHub",
subText: "Like what we're doing? You know what to do! :)",
complete: hasUserStarred,
icon: faStar,
time: "1 min",
userAction: "star_cta_clicked",
link: "https://github.com/Infisical/infisical"
})}
</div>
</div>
);
}
Home.requireAuth = true;
+6 -7
View File
@@ -10,12 +10,11 @@ import axios from "axios"
import InitialLoginStep from "@app/components/login/InitialLoginStep";
import MFAStep from "@app/components/login/MFAStep";
import PasswordInputStep from "@app/components/login/PasswordInputStep";
import { fetchUserDetails } from "@app/hooks/api/users/queries";
import { useProviderAuth } from "@app/hooks/useProviderAuth";
import { getAuthToken, isLoggedIn } from "@app/reactQuery";
import { fetchUserDetails } from "~/hooks/api/users/queries";
import getWorkspaces from "./api/workspace/getWorkspaces";
import getOrganizations from "./api/organization/getOrgs";
export default function Login() {
const router = useRouter();
@@ -44,10 +43,10 @@ export default function Login() {
useEffect(() => {
// TODO(akhilmhdh): workspace will be controlled by a workspace context
const redirectToDashboard = async () => {
let userWorkspace;
try {
const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0] && userWorkspaces[0]._id;
const userOrgs = await getOrganizations();
// userWorkspace = userWorkspaces[0] && userWorkspaces[0]._id;
const userOrg = userOrgs[0] && userOrgs[0]._id;
// user details
const userDetails = await fetchUserDetails()
@@ -62,7 +61,7 @@ export default function Login() {
const instance = axios.create()
await instance.post(cliUrl, { email: userDetails.email, privateKey: localStorage.getItem("PRIVATE_KEY"), JTWToken: getAuthToken() })
}
router.push(`/dashboard/${userWorkspace}`);
router.push(`/org/${userOrg}/overview`);
} catch (error) {
console.log("Error - Not logged in yet");
}
-55
View File
@@ -1,55 +0,0 @@
import { useEffect } from "react";
import Head from "next/head";
import Image from "next/image";
import { useRouter } from "next/router";
import Button from "@app/components/basic/buttons/Button";
import getOrganizationUserProjects from "./api/organization/GetOrgUserProjects";
export default function NoProjects() {
const router = useRouter();
const redirectUser = async () => {
const workspaces = await getOrganizationUserProjects({
orgId: String(localStorage.getItem("orgData.id"))
});
if (workspaces.length > 0) {
router.push(`/dashboard/${workspaces[0]._id}`);
}
};
useEffect(() => {
// on initial load - run auth check
(async () => {
await redirectUser();
})();
}, []);
return (
<div className="mr-auto flex h-full w-11/12 flex-col items-center justify-center text-center text-lg text-gray-300">
<Head>
<title>No Projects | Infisical</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
</Head>
<div className="mb-6 mt-16 mr-16">
<Image src="/images/dragon-cant-find.svg" height={270} width={436} alt="google logo" />
</div>
<div className="mb-8 rounded-md bg-mineshaft-900 border border-mineshaft-700 px-4 py-6 text-bunker-300 shadow-xl">
<div className="max-w-md">
You are not part of any projects in this organization yet. When you are, they will appear
here.
</div>
<div className="mt-4 max-w-md">
Create a new project, or ask other organization members to give you necessary permissions.
</div>
<div className="mx-2 mt-6">
<Button text="Check again" onButtonPressed={redirectUser} size="md" color="mineshaft" />
</div>
</div>
</div>
);
}
NoProjects.requireAuth = true;
@@ -0,0 +1,21 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { useTranslation } from "react-i18next";
import Head from "next/head";
import { MembersPage } from "@app/views/Org/MembersPage";
export default function SettingsOrg() {
const { t } = useTranslation();
return (
<>
<Head>
<title>{t("common.head-title", { title: t("settings.org.title") })}</title>
<link rel="icon" href="/infisical.ico" />
</Head>
<MembersPage />
</>
);
}
SettingsOrg.requireAuth = true;
@@ -1,51 +1,30 @@
import crypto from "crypto";
import { useEffect, useState } from "react";
import { Controller, useForm } from "react-hook-form";
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 { faFolderOpen } from "@fortawesome/free-regular-svg-icons";
import { faArrowRight, faCheckCircle, faHandPeace, faMagnifyingGlass, faNetworkWired, faPlug, faPlus, faUserPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
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 { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import onboardingCheck from "@app/components/utilities/checks/OnboardingCheck";
import { Button, Checkbox, FormControl, Input, Modal, ModalContent, UpgradePlanModal } from "@app/components/v2";
import { TabsObject } from "@app/components/v2/Tabs";
import { useSubscription, useUser, useWorkspace } from "@app/context";
import { fetchOrgUsers, useAddUserToWs, useCreateWorkspace, useUploadWsKey } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
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;
}
import { encryptAssymmetric } from "../../../../components/utilities/cryptography/crypto";
import registerUserAction from "../../../api/userActions/registerUserAction";
const features = [{
"_id": 0,
@@ -154,131 +133,110 @@ const learningItem = ({
);
};
const formSchema = yup.object({
name: yup.string().required().label("Project Name").trim(),
addMembers: yup.bool().required().label("Add Members")
});
type TAddProjectFormData = yup.InferType<typeof formSchema>;
// #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();
const { workspaces,
// isLoading: isWorkspaceLoading
} = useWorkspace();
const currentOrg = String(router.query.id);
const { createNotification } = useNotificationContext();
const addWsUser = useAddUserToWs();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"addNewWs",
"upgradePlan"
] as const);
const {
control,
formState: { isSubmitting },
reset,
handleSubmit
} = useForm<TAddProjectFormData>({
resolver: yupResolver(formSchema)
});
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);
const [searchFilter, setSearchFilter] = useState("");
const createWs = useCreateWorkspace();
const { user } = useUser();
const uploadWsKey = useUploadWsKey();
const onCreateProject = async ({ name, addMembers }: TAddProjectFormData) => {
// type check
if (!currentOrg) return;
try {
const {
data: {
workspace: { _id: newWorkspaceId }
}
} = await createWs.mutateAsync({
organizationId: currentOrg,
workspaceName: name
});
const randomBytes = crypto.randomBytes(16).toString("hex");
const PRIVATE_KEY = String(localStorage.getItem("PRIVATE_KEY"));
const { ciphertext, nonce } = encryptAssymmetric({
plaintext: randomBytes,
publicKey: user.publicKey,
privateKey: PRIVATE_KEY
});
await uploadWsKey.mutateAsync({
encryptedKey: ciphertext,
nonce,
userId: user?._id,
workspaceId: newWorkspaceId
});
if (addMembers) {
// not using hooks because need at this point only
const orgUsers = await fetchOrgUsers(currentOrg);
orgUsers.forEach(({ status, user: orgUser }) => {
// skip if status of org user is not accepted
// this orgUser is the person who created the ws
if (status !== "accepted" || user.email === orgUser.email) return;
addWsUser.mutate({ email: orgUser.email, workspaceId: newWorkspaceId });
});
}
createNotification({ text: "Workspace created", type: "success" });
handlePopUpClose("addNewWs");
router.push(`/project/${newWorkspaceId}/secrets`);
} catch (err) {
console.error(err);
createNotification({ text: "Failed to create workspace", type: "error" });
}
};
const { subscription } = useSubscription();
const isAddingProjectsAllowed = subscription?.workspaceLimit ? (subscription.workspacesUsed < subscription.workspaceLimit) : true;
useEffect(() => {
onboardingCheck({
setHasUserClickedIntro,
setHasUserClickedSlack,
setHasUserPushedSecrets,
setHasUserStarred,
setUsersInOrg
});
}, []);
return userList ? (
return (
<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>
@@ -286,15 +244,50 @@ export default function Organization() {
</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="w-full flex flex-row mt-6">
<Input
className="h-[2.3rem] text-sm bg-mineshaft-800 placeholder-mineshaft-50 duration-200 focus:bg-mineshaft-700/80"
placeholder="Search by project name..."
value={searchFilter}
onChange={(e) => setSearchFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
/>
<Button
colorSchema="primary"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
onClick={() => {
if (isAddingProjectsAllowed) {
handlePopUpOpen("addNewWs")
} else {
handlePopUpOpen("upgradePlan");
}
}}
className="ml-2"
>
Add New Project
</Button>
</div>
<div className="mt-4 w-full grid gap-4 grid-cols-3 grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4">
{workspaces.filter(ws => ws.name.toLowerCase().includes(searchFilter.toLowerCase())).map(workspace => <div key={workspace._id} className="h-32 lg:h-40 min-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="text-sm text-mineshaft-300 mt-0 lg:pb-6">{(workspace.environments?.length || 0)} environments</div>
<Link href={`/project/${workspace._id}/secrets`}>
<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>
{workspaces.length === 0 && (
<div className="w-full rounded-md bg-mineshaft-800 border border-mineshaft-700 px-4 py-6 text-mineshaft-300 text-base">
<FontAwesomeIcon icon={faFolderOpen} className="w-full text-center text-5xl mb-4 mt-2 text-mineshaft-400" />
<div className="text-center font-light">
You are not part of any projects in this organization yet. When you are, they will appear
here.
</div>
<div className="mt-0.5 text-center font-light">
Create a new project, or ask other organization members to give you necessary permissions.
</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>
@@ -314,9 +307,9 @@ export default function Organization() {
icon: faPlus,
time: "1 min",
userAction: "first_time_secrets_pushed",
link: `/dashboard/${router.query.id}`
link: `/project/${workspaces[0]?._id}/secrets`
})}
<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="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-mineshaft-800 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" />
@@ -362,7 +355,7 @@ export default function Organization() {
icon: faSlack,
time: "1 min",
userAction: "slack_cta_clicked",
link: "https://join.slack.com/t/infisical-users/shared_invite/zt-1wehzfnzn-1aMo5JcGENJiNAC2SD8Jlg"
link: "https://join.slack.com/t/infisical-users/shared_invite/zt-1ye0tm8ab-899qZ6ZbpfESuo6TEikyOQ"
})}
{/* <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">
@@ -381,51 +374,89 @@ export default function Organization() {
<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>
<a
target="_blank"
rel="noopener noreferrer"
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"
href="https://infisical.com/docs/documentation/getting-started/kubernetes"
>
Learn more <FontAwesomeIcon icon={faArrowRight} className="pl-1.5 pr-0.5 group-hover:pl-2 group-hover:pr-0 duration-200"/>
</a>
</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}
<Modal
isOpen={popUp.addNewWs.isOpen}
onOpenChange={(isModalOpen) => {
handlePopUpToggle("addNewWs", isModalOpen);
reset();
}}
>
<ModalContent
title="Create a new project"
subTitle="This project will contain your secrets and configurations."
>
<form onSubmit={handleSubmit(onCreateProject)}>
<Controller
control={control}
name="name"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Project Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="Type your project name" />
</FormControl>
)}
/>
<div className="mt-4 pl-1">
<Controller
control={control}
name="addMembers"
defaultValue
render={({ field: { onBlur, value, onChange } }) => (
<Checkbox
id="add-project-layout"
isChecked={value}
onCheckedChange={onChange}
onBlur={onBlur}
>
Add all members of my organization to this project
</Checkbox>
)}
/>
</div>
<div className="mt-7 flex items-center">
<Button
isDisabled={isSubmitting}
isLoading={isSubmitting}
key="layout-create-project-submit"
className="mr-4"
type="submit"
>
Create Project
</Button>
<Button
key="layout-cancel-create-project"
onClick={() => handlePopUpClose("addNewWs")}
variant="plain"
colorSchema="secondary"
>
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
<UpgradePlanModal
isOpen={popUp.upgradePlan.isOpen}
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
text="You have exceeded the number of projects allowed on the free plan."
/>
{/* <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>
);
}
@@ -5,14 +5,13 @@ import { useRouter } from "next/router";
import Button from "@app/components/basic/buttons/Button";
import EventFilter from "@app/components/basic/EventFilter";
import NavHeader from "@app/components/navigation/NavHeader";
import { UpgradePlanModal } from "@app/components/v2";
import { useSubscription } from "@app/context";
import ActivitySideBar from "@app/ee/components/ActivitySideBar";
import { usePopUp } from "@app/hooks/usePopUp";
import getProjectLogs from "../../ee/api/secrets/GetProjectLogs";
import ActivityTable from "../../ee/components/ActivityTable";
import getProjectLogs from "../../../../ee/api/secrets/GetProjectLogs";
import ActivityTable from "../../../../ee/components/ActivityTable";
interface LogData {
_id: string;
@@ -152,20 +151,17 @@ export default function Activity() {
};
return (
<div className="mx-6 lg:mx-0 w-full h-full">
<div className="mx-auto w-full h-full max-w-7xl">
<Head>
<title>Audit Logs</title>
<link rel="icon" href="/infisical.ico" />
<meta property="og:image" content="/images/message.png" />
</Head>
<div className="ml-6">
<NavHeader pageName="Audit Logs" isProjectRelated />
</div>
{currentSidebarAction && (
<ActivitySideBar toggleSidebar={toggleSidebar} currentAction={currentSidebarAction} />
)}
<div className="flex flex-col justify-between items-start mx-4 mt-6 mb-4 text-xl max-w-5xl px-2">
<div className="flex flex-row justify-start items-center text-3xl">
<div className="flex flex-col justify-between items-start mx-4 mb-4 text-xl px-2">
<div className="flex flex-row justify-start items-center text-3xl mt-6">
<p className="font-semibold mr-4 text-bunker-100">{t("activity.title")}</p>
</div>
<p className="mr-4 text-base text-gray-400">{t("activity.subtitle")}</p>
@@ -9,20 +9,19 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import Button from "@app/components/basic/buttons/Button";
import AddProjectMemberDialog from "@app/components/basic/dialog/AddProjectMemberDialog";
import ProjectUsersTable from "@app/components/basic/table/ProjectUsersTable";
import NavHeader from "@app/components/navigation/NavHeader";
import guidGenerator from "@app/components/utilities/randomId";
import { Input } from "@app/components/v2";
import {
decryptAssymmetric,
encryptAssymmetric
} from "../../components/utilities/cryptography/crypto";
import getOrganizationUsers from "../api/organization/GetOrgUsers";
import getUser from "../api/user/getUser";
} from "../../../../components/utilities/cryptography/crypto";
import getOrganizationUsers from "../../../api/organization/GetOrgUsers";
import getUser from "../../../api/user/getUser";
// 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";
import addUserToWorkspace from "../../../api/workspace/addUserToWorkspace";
import getWorkspaceUsers from "../../../api/workspace/getWorkspaceUsers";
import uploadKeys from "../../../api/workspace/uploadKeys";
interface UserProps {
firstName: string;
@@ -149,14 +148,11 @@ export default function Users() {
};
return userList ? (
<div className="flex max-w-[calc(100vw-240px)] flex-col justify-start bg-bunker-800 md:h-screen">
<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="ml-6">
<NavHeader pageName={t("settings.members.title")} isProjectRelated />
</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">{t("settings.members.title")}</p>
</div>
@@ -174,7 +170,7 @@ export default function Users() {
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 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"
@@ -194,7 +190,7 @@ export default function Users() {
/>
</div>
</div>
<div className="no-scrollbar::-webkit-scrollbar block overflow-x-scroll px-6 pb-6 no-scrollbar">
<div className="block overflow-x-auto px-6 pb-6">
<ProjectUsersTable
userData={userList}
changeData={setUserList}
+5 -5
View File
@@ -8,7 +8,7 @@ import { useRouter } from "next/router";
import { Button, Input } from "@app/components/v2";
import { isLoggedIn } from "@app/reactQuery";
import getWorkspaces from "./api/workspace/getWorkspaces";
import getOrganizations from "./api/organization/getOrgs";
export default function Login() {
const router = useRouter();
@@ -18,11 +18,11 @@ export default function Login() {
useEffect(() => {
// TODO(akhilmhdh): workspace will be controlled by a workspace context
const redirectToDashboard = async () => {
let userWorkspace;
let userOrg;
try {
const userWorkspaces = await getWorkspaces();
userWorkspace = userWorkspaces[0] && userWorkspaces[0]._id;
router.push(`/dashboard/${userWorkspace}`);
const userOrgs = await getOrganizations();
userOrg = userOrgs[0] && userOrgs[0]._id;
router.push(`/org/${userOrg}/overview`);
} catch (error) {
console.log("Error - Not logged in yet");
}
+5 -5
View File
@@ -16,7 +16,7 @@ import { useFetchServerStatus } from "@app/hooks/api/serverDetails";
import { useProviderAuth } from "@app/hooks/useProviderAuth";
import checkEmailVerificationCode from "./api/auth/CheckEmailVerificationCode";
import getWorkspaces from "./api/workspace/getWorkspaces";
import getOrganizations from "./api/organization/getOrgs";
/**
* @returns the signup page
@@ -48,8 +48,8 @@ export default function SignUp() {
useEffect(() => {
const tryAuth = async () => {
try {
const userWorkspaces = await getWorkspaces();
router.push(`/dashboard/${userWorkspaces[0]._id}`);
const userOrgs = await getOrganizations();
router.push(`/org/${userOrgs[0]._id}/overview`);
} catch (error) {
console.log("Error - Not logged in yet");
}
@@ -90,8 +90,8 @@ export default function SignUp() {
}
if (!serverDetails?.emailConfigured && step === 5) {
getWorkspaces().then((userWorkspaces) => {
router.push(`/dashboard/${userWorkspaces[0]._id}`);
getOrganizations().then((userOrgs) => {
router.push(`/org/${userOrgs[0]._id}/overview`);
});
}
}, [step]);
+3 -14
View File
@@ -46,6 +46,7 @@ type Errors = {
export default function SignupInvite() {
const { data: commonPasswords } = useGetCommonPasswords();
const [password, setPassword] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
@@ -212,7 +213,7 @@ export default function SignupInvite() {
} else {
// user will be redirected to dashboard
// if not logged in gets kicked out to login
router.push("/dashboard");
router.push(`/org/${organizationId}/overview`);
}
} else {
console.log("ERROR", response);
@@ -338,22 +339,10 @@ export default function SignupInvite() {
setBackupKeyError,
setBackupKeyIssued
});
router.push("/noprojects/");
router.push(`/org/${organizationId}/overview`);
}}
size="lg"
/>
{/* <div
className="text-l mt-4 text-lg text-gray-400 hover:text-gray-300 duration-200 bg-white/5 px-8 hover:bg-white/10 py-3 rounded-md cursor-pointer"
onClick={() => {
if (localStorage.getItem("projectData.id")) {
router.push("/dashboard/" + localStorage.getItem("projectData.id"));
} else {
router.push("/noprojects")
}
}}
>
Later
</div> */}
</div>
</div>
);