Merge pull request #1085 from rtpa25/feat/create-multiple-orgs-under-same-account

feat: adds ability to create multiple orgs under the same account
This commit is contained in:
Akhil Mohan
2023-10-31 11:21:56 +05:30
committed by GitHub
5 changed files with 231 additions and 189 deletions
+50 -53
View File
@@ -19,7 +19,8 @@ const organizationKeys = {
getUserOrganizations: ["organization"] as const, getUserOrganizations: ["organization"] as const,
getOrgPlanBillingInfo: (orgId: string) => [{ orgId }, "organization-plan-billing"] as const, getOrgPlanBillingInfo: (orgId: string) => [{ orgId }, "organization-plan-billing"] as const,
getOrgPlanTable: (orgId: string) => [{ orgId }, "organization-plan-table"] as const, getOrgPlanTable: (orgId: string) => [{ orgId }, "organization-plan-table"] as const,
getOrgPlansTable: (orgId: string, billingCycle: "monthly" | "yearly") => [{ orgId, billingCycle }, "organization-plans-table"] as const, getOrgPlansTable: (orgId: string, billingCycle: "monthly" | "yearly") =>
[{ orgId, billingCycle }, "organization-plans-table"] as const,
getOrgBillingDetails: (orgId: string) => [{ orgId }, "organization-billing-details"] as const, getOrgBillingDetails: (orgId: string) => [{ orgId }, "organization-billing-details"] as const,
getOrgPmtMethods: (orgId: string) => [{ orgId }, "organization-pmt-methods"] as const, getOrgPmtMethods: (orgId: string) => [{ orgId }, "organization-pmt-methods"] as const,
getOrgTaxIds: (orgId: string) => [{ orgId }, "organization-tax-ids"] as const, getOrgTaxIds: (orgId: string) => [{ orgId }, "organization-tax-ids"] as const,
@@ -28,9 +29,11 @@ const organizationKeys = {
}; };
export const fetchOrganizations = async () => { export const fetchOrganizations = async () => {
const { data: { organizations } } = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization"); const {
data: { organizations }
} = await apiRequest.get<{ organizations: Organization[] }>("/api/v1/organization");
return organizations; return organizations;
} };
export const useGetOrganizations = () => { export const useGetOrganizations = () => {
return useQuery({ return useQuery({
@@ -39,23 +42,23 @@ export const useGetOrganizations = () => {
return fetchOrganizations(); return fetchOrganizations();
} }
}); });
} };
export const useCreateOrg = () => { export const useCreateOrg = () => {
const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({ name }: { name: string }) => {
name const {
}: { data: { organization }
name: string; } = await apiRequest.post("/api/v2/organizations", {
}) => { name
const { data: { organization } } = await apiRequest.post( });
"/api/v2/organizations",
{
name
}
);
return organization; return organization;
},
onSuccess: () => {
queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
} }
}); });
}; };
@@ -75,16 +78,12 @@ export const useRenameOrg = () => {
export const useGetOrgTrialUrl = () => { export const useGetOrgTrialUrl = () => {
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({ orgId, success_url }: { orgId: string; success_url: string }) => {
orgId, const {
success_url data: { url }
}: { } = await apiRequest.post(`/api/v1/organizations/${orgId}/session/trial`, {
orgId: string;
success_url: string;
}) => {
const { data: { url } } = await apiRequest.post(`/api/v1/organizations/${orgId}/session/trial`, {
success_url success_url
}) });
return url; return url;
} }
@@ -103,7 +102,7 @@ export const useGetOrgPlanBillingInfo = (organizationId: string) => {
}, },
enabled: true enabled: true
}); });
} };
export const useGetOrgPlanTable = (organizationId: string) => { export const useGetOrgPlanTable = (organizationId: string) => {
return useQuery({ return useQuery({
@@ -117,14 +116,14 @@ export const useGetOrgPlanTable = (organizationId: string) => {
}, },
enabled: true enabled: true
}); });
} };
export const useGetOrgPlansTable = ({ export const useGetOrgPlansTable = ({
organizationId, organizationId,
billingCycle billingCycle
}: { }: {
organizationId: string; organizationId: string;
billingCycle: "monthly" | "yearly" billingCycle: "monthly" | "yearly";
}) => { }) => {
return useQuery({ return useQuery({
queryKey: organizationKeys.getOrgPlansTable(organizationId, billingCycle), queryKey: organizationKeys.getOrgPlansTable(organizationId, billingCycle),
@@ -137,7 +136,7 @@ export const useGetOrgPlansTable = ({
}, },
enabled: true enabled: true
}); });
} };
export const useGetOrgBillingDetails = (organizationId: string) => { export const useGetOrgBillingDetails = (organizationId: string) => {
return useQuery({ return useQuery({
@@ -151,7 +150,7 @@ export const useGetOrgBillingDetails = (organizationId: string) => {
}, },
enabled: true enabled: true
}); });
} };
export const useUpdateOrgBillingDetails = () => { export const useUpdateOrgBillingDetails = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@@ -193,7 +192,7 @@ export const useGetOrgPmtMethods = (organizationId: string) => {
}, },
enabled: true enabled: true
}); });
} };
export const useAddOrgPmtMethod = () => { export const useAddOrgPmtMethod = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@@ -208,7 +207,9 @@ export const useAddOrgPmtMethod = () => {
success_url: string; success_url: string;
cancel_url: string; cancel_url: string;
}) => { }) => {
const { data: { url } } = await apiRequest.post( const {
data: { url }
} = await apiRequest.post(
`/api/v1/organizations/${organizationId}/billing-details/payment-methods`, `/api/v1/organizations/${organizationId}/billing-details/payment-methods`,
{ {
success_url, success_url,
@@ -230,7 +231,7 @@ export const useDeleteOrgPmtMethod = () => {
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({
organizationId, organizationId,
pmtMethodId, pmtMethodId
}: { }: {
organizationId: string; organizationId: string;
pmtMethodId: string; pmtMethodId: string;
@@ -245,7 +246,7 @@ export const useDeleteOrgPmtMethod = () => {
queryClient.invalidateQueries(organizationKeys.getOrgPmtMethods(dto.organizationId)); queryClient.invalidateQueries(organizationKeys.getOrgPmtMethods(dto.organizationId));
} }
}); });
} };
export const useGetOrgTaxIds = (organizationId: string) => { export const useGetOrgTaxIds = (organizationId: string) => {
return useQuery({ return useQuery({
@@ -259,7 +260,7 @@ export const useGetOrgTaxIds = (organizationId: string) => {
}, },
enabled: true enabled: true
}); });
} };
export const useAddOrgTaxId = () => { export const useAddOrgTaxId = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
@@ -294,13 +295,7 @@ export const useDeleteOrgTaxId = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({ organizationId, taxId }: { organizationId: string; taxId: string }) => {
organizationId,
taxId,
}: {
organizationId: string;
taxId: string;
}) => {
const { data } = await apiRequest.delete( const { data } = await apiRequest.delete(
`/api/v1/organizations/${organizationId}/billing-details/tax-ids/${taxId}` `/api/v1/organizations/${organizationId}/billing-details/tax-ids/${taxId}`
); );
@@ -311,7 +306,7 @@ export const useDeleteOrgTaxId = () => {
queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId)); queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId));
} }
}); });
} };
export const useGetOrgInvoices = (organizationId: string) => { export const useGetOrgInvoices = (organizationId: string) => {
return useQuery({ return useQuery({
@@ -325,7 +320,7 @@ export const useGetOrgInvoices = (organizationId: string) => {
}, },
enabled: true enabled: true
}); });
} };
export const useCreateCustomerPortalSession = () => { export const useCreateCustomerPortalSession = () => {
return useMutation({ return useMutation({
@@ -352,18 +347,16 @@ export const useGetOrgLicenses = (organizationId: string) => {
}, },
enabled: true enabled: true
}); });
} };
export const useDeleteOrgById = () => { export const useDeleteOrgById = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
return useMutation({ return useMutation({
mutationFn: async ({ mutationFn: async ({ organizationId }: { organizationId: string }) => {
organizationId, const {
}: { data: { organization }
organizationId: string; } = await apiRequest.delete<{ organization: Organization }>(
}) => {
const { data: { organization } } = await apiRequest.delete<{ organization: Organization }>(
`/api/v2/organizations/${organizationId}` `/api/v2/organizations/${organizationId}`
); );
return organization; return organization;
@@ -372,8 +365,12 @@ export const useDeleteOrgById = () => {
queryClient.invalidateQueries(organizationKeys.getUserOrganizations); queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
queryClient.invalidateQueries(organizationKeys.getOrgPlanBillingInfo(dto.organizationId)); queryClient.invalidateQueries(organizationKeys.getOrgPlanBillingInfo(dto.organizationId));
queryClient.invalidateQueries(organizationKeys.getOrgPlanTable(dto.organizationId)); queryClient.invalidateQueries(organizationKeys.getOrgPlanTable(dto.organizationId));
queryClient.invalidateQueries(organizationKeys.getOrgPlansTable(dto.organizationId, "monthly")); // You might need to invalidate for 'yearly' as well. queryClient.invalidateQueries(
queryClient.invalidateQueries(organizationKeys.getOrgPlansTable(dto.organizationId, "yearly")); organizationKeys.getOrgPlansTable(dto.organizationId, "monthly")
); // You might need to invalidate for 'yearly' as well.
queryClient.invalidateQueries(
organizationKeys.getOrgPlansTable(dto.organizationId, "yearly")
);
queryClient.invalidateQueries(organizationKeys.getOrgBillingDetails(dto.organizationId)); queryClient.invalidateQueries(organizationKeys.getOrgBillingDetails(dto.organizationId));
queryClient.invalidateQueries(organizationKeys.getOrgPmtMethods(dto.organizationId)); queryClient.invalidateQueries(organizationKeys.getOrgPmtMethods(dto.organizationId));
queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId)); queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId));
@@ -381,4 +378,4 @@ export const useDeleteOrgById = () => {
queryClient.invalidateQueries(organizationKeys.getOrgLicenses(dto.organizationId)); queryClient.invalidateQueries(organizationKeys.getOrgLicenses(dto.organizationId));
} }
}); });
} };
+51 -13
View File
@@ -70,7 +70,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;
@@ -133,7 +135,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,
@@ -150,7 +153,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 () => {
@@ -316,6 +319,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>
@@ -488,7 +507,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>
)} )}
@@ -599,17 +618,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>
@@ -617,9 +650,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>
@@ -778,6 +812,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>
+11 -109
View File
@@ -1,114 +1,16 @@
import { useEffect } 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";
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();
export type FormData = yup.InferType<typeof schema>;
export const NonePage = () => { export const NonePage = () => {
const { createNotification } = useNotificationContext(); const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const);
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"createOrg",
] as const);
const { mutateAsync } = useCreateOrg(); return (
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
const { <CreateOrgModal
control, isOpen={popUp.createOrg.isOpen}
handleSubmit, onClose={() => handlePopUpToggle("createOrg", false)}
reset, />
formState: { isSubmitting } </div>
} = useForm<FormData>({ );
resolver: yupResolver(schema), };
defaultValues: {
name: ""
}
});
useEffect(() => {
handlePopUpOpen("createOrg");
}, []);
const onFormSubmit = async ({ name }: FormData) => {
try {
const organization = await mutateAsync({
name
});
localStorage.setItem("orgData.id", organization._id);
createNotification({
text: "Successfully created organization",
type: "success"
});
window.location.href = `/org/${organization._id}/overview`;
reset();
handlePopUpToggle("createOrg", false);
} catch (err) {
console.error(err);
createNotification({
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,104 @@
import { FC } from "react";
import { Controller, useForm } from "react-hook-form";
import { useRouter } from "next/router";
import { zodResolver } from "@hookform/resolvers/zod";
import z from "zod";
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 = z
.object({
name: z.string().nonempty({ message: "Name is required" })
})
.required();
export type FormData = z.infer<typeof schema>;
interface CreateOrgModalProps {
isOpen: boolean;
onClose: () => void;
}
export const CreateOrgModal: FC<CreateOrgModalProps> = ({ isOpen, onClose }) => {
const { createNotification } = useNotificationContext();
const router = useRouter();
const {
control,
handleSubmit,
reset,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: zodResolver(schema),
defaultValues: {
name: ""
}
});
const { mutateAsync } = useCreateOrg();
const onFormSubmit = async ({ name }: FormData) => {
try {
const organization = await mutateAsync({
name
});
createNotification({
text: "Successfully created organization",
type: "success"
});
if (router.isReady) router.push(`/org/${organization._id}/overview`);
else window.location.href = `/org/${organization._id}/overview`;
localStorage.setItem("orgData.id", organization._id);
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>
)}
/>
<div className="flex w-full gap-4">
<Button
className=""
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
Create
</Button>
<Button className="" size="sm" variant="outline_bg" type="button" onClick={onClose}>
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
};
@@ -0,0 +1 @@
export { CreateOrgModal } from "./CreateOrgModal";