mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 10:27:26 +00:00
feat: adds ability to create multiple orgs under the same account
This commit is contained in:
@@ -71,7 +71,9 @@ import {
|
|||||||
useGetUserAction,
|
useGetUserAction,
|
||||||
useLogoutUser,
|
useLogoutUser,
|
||||||
useRegisterUserAction,
|
useRegisterUserAction,
|
||||||
useUploadWsKey} from "@app/hooks/api";
|
useUploadWsKey
|
||||||
|
} from "@app/hooks/api";
|
||||||
|
import { CreateOrgModal } from "@app/views/Org/components";
|
||||||
|
|
||||||
interface LayoutProps {
|
interface LayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -115,12 +117,12 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
// eslint-disable-next-line prefer-const
|
// eslint-disable-next-line prefer-const
|
||||||
const { workspaces, currentWorkspace } = useWorkspace();
|
const { workspaces, currentWorkspace } = useWorkspace();
|
||||||
const { orgs, currentOrg } = useOrganization();
|
const { orgs, currentOrg } = useOrganization();
|
||||||
|
|
||||||
const { user } = useUser();
|
const { user } = useUser();
|
||||||
const { subscription } = useSubscription();
|
const { subscription } = useSubscription();
|
||||||
const workspaceId = currentWorkspace?._id || "";
|
const workspaceId = currentWorkspace?._id || "";
|
||||||
const { data: updateClosed } = useGetUserAction("september_update_closed");
|
const { data: updateClosed } = useGetUserAction("september_update_closed");
|
||||||
|
|
||||||
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ workspaceId });
|
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ workspaceId });
|
||||||
|
|
||||||
const isAddingProjectsAllowed = subscription?.workspaceLimit
|
const isAddingProjectsAllowed = subscription?.workspaceLimit
|
||||||
@@ -134,7 +136,8 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
|
|
||||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||||
"addNewWs",
|
"addNewWs",
|
||||||
"upgradePlan"
|
"upgradePlan",
|
||||||
|
"createOrg"
|
||||||
] as const);
|
] as const);
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
@@ -151,7 +154,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
|
|
||||||
const closeUpdate = async () => {
|
const closeUpdate = async () => {
|
||||||
await registerUserAction.mutateAsync("september_update_closed");
|
await registerUserAction.mutateAsync("september_update_closed");
|
||||||
}
|
};
|
||||||
|
|
||||||
const logout = useLogoutUser();
|
const logout = useLogoutUser();
|
||||||
const logOutUser = async () => {
|
const logOutUser = async () => {
|
||||||
@@ -318,6 +321,22 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
|
<DropdownMenuItem key="add-org">
|
||||||
|
<Button
|
||||||
|
onClick={() => handlePopUpOpen("createOrg")}
|
||||||
|
variant="plain"
|
||||||
|
colorSchema="secondary"
|
||||||
|
size="xs"
|
||||||
|
className="flex w-full items-center justify-start p-0 font-normal"
|
||||||
|
leftIcon={
|
||||||
|
<FontAwesomeIcon icon={faPlus} className="mr-3 text-primary" />
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<div className="flex w-full items-center justify-between">
|
||||||
|
Create New Organization
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuItem>
|
||||||
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
<div className="mt-1 h-1 border-t border-mineshaft-600" />
|
||||||
<button type="button" onClick={logOutUser} className="w-full">
|
<button type="button" onClick={logOutUser} className="w-full">
|
||||||
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
||||||
@@ -490,7 +509,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
>
|
>
|
||||||
Secret approvals
|
Secret approvals
|
||||||
{Boolean(secretApprovalReqCount?.open) && (
|
{Boolean(secretApprovalReqCount?.open) && (
|
||||||
<span className="text-xs font-semibold py-0.5 px-1 rounded ml-2 bg-primary-600 border border-primary-400 text-black">
|
<span className="ml-2 rounded border border-primary-400 bg-primary-600 py-0.5 px-1 text-xs font-semibold text-black">
|
||||||
{secretApprovalReqCount?.open}
|
{secretApprovalReqCount?.open}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
@@ -601,17 +620,31 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[10.7rem] ${router.asPath.includes("org") ? "bottom-[8.15rem]" : "bottom-[5.15rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-50`}/>
|
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[10.7rem] ${router.asPath.includes("org") ? "bottom-[8.15rem]" : "bottom-[5.15rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-50`}/>
|
||||||
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[11.5rem] ${router.asPath.includes("org") ? "bottom-[7.9rem]" : "bottom-[4.9rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-70`}/>
|
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[11.5rem] ${router.asPath.includes("org") ? "bottom-[7.9rem]" : "bottom-[4.9rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-70`}/>
|
||||||
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[12.3rem] ${router.asPath.includes("org") ? "bottom-[7.65rem]" : "bottom-[4.65rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-90`}/> */}
|
<div className={`${isLearningNoteOpen ? "block" : "hidden"} z-0 absolute h-60 w-[12.3rem] ${router.asPath.includes("org") ? "bottom-[7.65rem]" : "bottom-[4.65rem]"} bg-mineshaft-900 border border-mineshaft-600 mb-4 rounded-md opacity-90`}/> */}
|
||||||
<div className={`${!updateClosed ? "block" : "hidden"} relative z-10 h-64 w-52 bg-mineshaft-900 border border-mineshaft-600 mb-6 rounded-md flex flex-col items-center justify-start px-3`}>
|
<div
|
||||||
<div className="w-full mt-2 text-md text-mineshaft-100 font-semibold">Infisical September update</div>
|
className={`${
|
||||||
<div className="w-full mt-1 text-sm text-mineshaft-300 font-normal leading-[1.2rem] mb-1">Improved RBAC, new integrations, dashboard remake, and more!</div>
|
!updateClosed ? "block" : "hidden"
|
||||||
<div className="h-[6.77rem] w-full rounded-md mt-2 border border-mineshaft-700">
|
} relative z-10 mb-6 flex h-64 w-52 flex-col items-center justify-start rounded-md border border-mineshaft-600 bg-mineshaft-900 px-3`}
|
||||||
<Image src="/images/infisical-update-september-2023.png" height={319} width={539} alt="kubernetes image" className="rounded-sm" />
|
>
|
||||||
|
<div className="text-md mt-2 w-full font-semibold text-mineshaft-100">
|
||||||
|
Infisical September update
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full flex justify-between items-center mt-3 px-0.5">
|
<div className="mt-1 mb-1 w-full text-sm font-normal leading-[1.2rem] text-mineshaft-300">
|
||||||
|
Improved RBAC, new integrations, dashboard remake, and more!
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 h-[6.77rem] w-full rounded-md border border-mineshaft-700">
|
||||||
|
<Image
|
||||||
|
src="/images/infisical-update-september-2023.png"
|
||||||
|
height={319}
|
||||||
|
width={539}
|
||||||
|
alt="kubernetes image"
|
||||||
|
className="rounded-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="mt-3 flex w-full items-center justify-between px-0.5">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => closeUpdate()}
|
onClick={() => closeUpdate()}
|
||||||
className="text-mineshaft-400 hover:text-mineshaft-100 duration-200"
|
className="text-mineshaft-400 duration-200 hover:text-mineshaft-100"
|
||||||
>
|
>
|
||||||
Close
|
Close
|
||||||
</button>
|
</button>
|
||||||
@@ -619,9 +652,10 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
href="https://infisical.com/blog/infisical-update-september-2023"
|
href="https://infisical.com/blog/infisical-update-september-2023"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-sm text-mineshaft-400 font-normal leading-[1.2rem] hover:text-mineshaft-100 duration-200"
|
className="text-sm font-normal leading-[1.2rem] text-mineshaft-400 duration-200 hover:text-mineshaft-100"
|
||||||
>
|
>
|
||||||
Learn More <FontAwesomeIcon icon={faArrowUpRightFromSquare} className="text-xs pl-0.5"/>
|
Learn More{" "}
|
||||||
|
<FontAwesomeIcon icon={faArrowUpRightFromSquare} className="pl-0.5 text-xs" />
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -780,6 +814,10 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
|||||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||||
text="You have exceeded the number of projects allowed on the free plan."
|
text="You have exceeded the number of projects allowed on the free plan."
|
||||||
/>
|
/>
|
||||||
|
<CreateOrgModal
|
||||||
|
isOpen={popUp?.createOrg?.isOpen}
|
||||||
|
onClose={() => handlePopUpToggle("createOrg", false)}
|
||||||
|
/>
|
||||||
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
|
<main className="flex-1 overflow-y-auto overflow-x-hidden bg-bunker-800 dark:[color-scheme:dark]">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,112 +1,31 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Controller, useForm } from "react-hook-form";
|
|
||||||
import { yupResolver } from "@hookform/resolvers/yup";
|
|
||||||
import * as yup from "yup";
|
import * as yup from "yup";
|
||||||
|
|
||||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
FormControl,
|
|
||||||
Input,
|
|
||||||
Modal,
|
|
||||||
ModalContent} from "@app/components/v2";
|
|
||||||
import { useCreateOrg } from "@app/hooks/api";
|
|
||||||
import { usePopUp } from "@app/hooks/usePopUp";
|
import { usePopUp } from "@app/hooks/usePopUp";
|
||||||
|
|
||||||
const schema = yup.object({
|
import { CreateOrgModal } from "../components";
|
||||||
name: yup.string().required("Organization name is required"),
|
|
||||||
}).required();
|
const schema = yup
|
||||||
|
.object({
|
||||||
|
name: yup.string().required("Organization name is required")
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
export type FormData = yup.InferType<typeof schema>;
|
export type FormData = yup.InferType<typeof schema>;
|
||||||
|
|
||||||
export const NonePage = () => {
|
export const NonePage = () => {
|
||||||
const { createNotification } = useNotificationContext();
|
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["createOrg"] as const);
|
||||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
|
||||||
"createOrg",
|
|
||||||
] as const);
|
|
||||||
|
|
||||||
const { mutateAsync } = useCreateOrg();
|
|
||||||
|
|
||||||
const {
|
|
||||||
control,
|
|
||||||
handleSubmit,
|
|
||||||
reset,
|
|
||||||
formState: { isSubmitting }
|
|
||||||
} = useForm<FormData>({
|
|
||||||
resolver: yupResolver(schema),
|
|
||||||
defaultValues: {
|
|
||||||
name: ""
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
handlePopUpOpen("createOrg");
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const onFormSubmit = async ({ name }: FormData) => {
|
|
||||||
try {
|
|
||||||
|
|
||||||
const organization = await mutateAsync({
|
|
||||||
name
|
|
||||||
});
|
|
||||||
|
|
||||||
createNotification({
|
useEffect(() => {
|
||||||
text: "Successfully created organization",
|
handlePopUpOpen("createOrg");
|
||||||
type: "success"
|
}, []);
|
||||||
});
|
|
||||||
|
|
||||||
window.location.href = `/org/${organization._id}/overview`;
|
return (
|
||||||
|
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||||
reset();
|
<CreateOrgModal
|
||||||
handlePopUpToggle("createOrg", false);
|
isOpen={popUp.createOrg.isOpen}
|
||||||
} catch (err) {
|
onClose={() => handlePopUpToggle("createOrg", false)}
|
||||||
console.error(err);
|
/>
|
||||||
createNotification({
|
</div>
|
||||||
text: "Failed to created organization",
|
);
|
||||||
type: "error"
|
};
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="flex justify-center bg-bunker-800 text-white w-full h-full">
|
|
||||||
<Modal
|
|
||||||
isOpen={popUp?.createOrg?.isOpen}
|
|
||||||
>
|
|
||||||
<ModalContent
|
|
||||||
title="Create Organization"
|
|
||||||
subTitle="Looks like you're not part of any organizations. Create one to start using Infisical"
|
|
||||||
>
|
|
||||||
<form onSubmit={handleSubmit(onFormSubmit)}>
|
|
||||||
<Controller
|
|
||||||
control={control}
|
|
||||||
defaultValue=""
|
|
||||||
name="name"
|
|
||||||
render={({ field, fieldState: { error } }) => (
|
|
||||||
<FormControl
|
|
||||||
label="Name"
|
|
||||||
isError={Boolean(error)}
|
|
||||||
errorText={error?.message}
|
|
||||||
>
|
|
||||||
<Input
|
|
||||||
{...field}
|
|
||||||
placeholder="Acme Corp"
|
|
||||||
/>
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
className=""
|
|
||||||
size="sm"
|
|
||||||
type="submit"
|
|
||||||
isLoading={isSubmitting}
|
|
||||||
isDisabled={isSubmitting}
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import { FC } from "react";
|
||||||
|
import { Controller, useForm } from "react-hook-form";
|
||||||
|
import { yupResolver } from "@hookform/resolvers/yup";
|
||||||
|
import * as yup from "yup";
|
||||||
|
|
||||||
|
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||||
|
import { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
|
||||||
|
import { useCreateOrg } from "@app/hooks/api";
|
||||||
|
|
||||||
|
const schema = yup
|
||||||
|
.object({
|
||||||
|
name: yup.string().required("Organization name is required")
|
||||||
|
})
|
||||||
|
.required();
|
||||||
|
|
||||||
|
export type FormData = yup.InferType<typeof schema>;
|
||||||
|
|
||||||
|
interface CreateOrgModalProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) => {
|
||||||
|
const { createNotification } = useNotificationContext();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
handleSubmit,
|
||||||
|
reset,
|
||||||
|
formState: { isSubmitting }
|
||||||
|
} = useForm<FormData>({
|
||||||
|
resolver: yupResolver(schema),
|
||||||
|
defaultValues: {
|
||||||
|
name: ""
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const { mutateAsync } = useCreateOrg();
|
||||||
|
|
||||||
|
const onFormSubmit = async ({ name }: FormData) => {
|
||||||
|
try {
|
||||||
|
const organization = await mutateAsync({
|
||||||
|
name
|
||||||
|
});
|
||||||
|
|
||||||
|
createNotification({
|
||||||
|
text: "Successfully created organization",
|
||||||
|
type: "success"
|
||||||
|
});
|
||||||
|
|
||||||
|
window.location.href = `/org/${organization._id}/overview`;
|
||||||
|
|
||||||
|
reset();
|
||||||
|
onClose();
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
createNotification({
|
||||||
|
text: "Failed to created organization",
|
||||||
|
type: "error"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal isOpen={isOpen}>
|
||||||
|
<ModalContent
|
||||||
|
title="Create Organization"
|
||||||
|
subTitle="Looks like you're not part of any organizations. Create one to start using Infisical"
|
||||||
|
>
|
||||||
|
<form onSubmit={handleSubmit(onFormSubmit)}>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue=""
|
||||||
|
name="name"
|
||||||
|
render={({ field, fieldState: { error } }) => (
|
||||||
|
<FormControl label="Name" isError={Boolean(error)} errorText={error?.message}>
|
||||||
|
<Input {...field} placeholder="Acme Corp" />
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
className=""
|
||||||
|
size="sm"
|
||||||
|
type="submit"
|
||||||
|
isLoading={isSubmitting}
|
||||||
|
isDisabled={isSubmitting}
|
||||||
|
>
|
||||||
|
Create
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</ModalContent>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { CreateOrgModal } from "./CreateOrgModal";
|
||||||
Reference in New Issue
Block a user