mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
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:
@@ -2,11 +2,11 @@ import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
import { apiRequest } from "@app/config/request";
|
||||
|
||||
import {
|
||||
import {
|
||||
BillingDetails,
|
||||
Invoice,
|
||||
License,
|
||||
Organization,
|
||||
Organization,
|
||||
OrgPlanTable,
|
||||
PlanBillingInfo,
|
||||
PmtMethod,
|
||||
@@ -19,7 +19,8 @@ const organizationKeys = {
|
||||
getUserOrganizations: ["organization"] as const,
|
||||
getOrgPlanBillingInfo: (orgId: string) => [{ orgId }, "organization-plan-billing"] 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,
|
||||
getOrgPmtMethods: (orgId: string) => [{ orgId }, "organization-pmt-methods"] as const,
|
||||
getOrgTaxIds: (orgId: string) => [{ orgId }, "organization-tax-ids"] as const,
|
||||
@@ -28,34 +29,36 @@ const organizationKeys = {
|
||||
};
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
export const useGetOrganizations = () => {
|
||||
return useQuery({
|
||||
queryKey: organizationKeys.getUserOrganizations,
|
||||
return useQuery({
|
||||
queryKey: organizationKeys.getUserOrganizations,
|
||||
queryFn: async () => {
|
||||
return fetchOrganizations();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useCreateOrg = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
name
|
||||
}: {
|
||||
name: string;
|
||||
}) => {
|
||||
const { data: { organization } } = await apiRequest.post(
|
||||
"/api/v2/organizations",
|
||||
{
|
||||
name
|
||||
}
|
||||
);
|
||||
mutationFn: async ({ name }: { name: string }) => {
|
||||
const {
|
||||
data: { organization }
|
||||
} = await apiRequest.post("/api/v2/organizations", {
|
||||
name
|
||||
});
|
||||
|
||||
return organization;
|
||||
},
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -75,17 +78,13 @@ export const useRenameOrg = () => {
|
||||
|
||||
export const useGetOrgTrialUrl = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
orgId,
|
||||
success_url
|
||||
}: {
|
||||
orgId: string;
|
||||
success_url: string;
|
||||
}) => {
|
||||
const { data: { url } } = await apiRequest.post(`/api/v1/organizations/${orgId}/session/trial`, {
|
||||
mutationFn: async ({ orgId, success_url }: { orgId: string; success_url: string }) => {
|
||||
const {
|
||||
data: { url }
|
||||
} = await apiRequest.post(`/api/v1/organizations/${orgId}/session/trial`, {
|
||||
success_url
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
return url;
|
||||
}
|
||||
});
|
||||
@@ -99,11 +98,11 @@ export const useGetOrgPlanBillingInfo = (organizationId: string) => {
|
||||
`/api/v1/organizations/${organizationId}/plan/billing`
|
||||
);
|
||||
|
||||
return data;
|
||||
return data;
|
||||
},
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useGetOrgPlanTable = (organizationId: string) => {
|
||||
return useQuery({
|
||||
@@ -113,18 +112,18 @@ export const useGetOrgPlanTable = (organizationId: string) => {
|
||||
`/api/v1/organizations/${organizationId}/plan/table`
|
||||
);
|
||||
|
||||
return data;
|
||||
return data;
|
||||
},
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useGetOrgPlansTable = ({
|
||||
organizationId,
|
||||
billingCycle
|
||||
}: {
|
||||
organizationId: string;
|
||||
billingCycle: "monthly" | "yearly"
|
||||
billingCycle: "monthly" | "yearly";
|
||||
}) => {
|
||||
return useQuery({
|
||||
queryKey: organizationKeys.getOrgPlansTable(organizationId, billingCycle),
|
||||
@@ -133,11 +132,11 @@ export const useGetOrgPlansTable = ({
|
||||
`/api/v1/organizations/${organizationId}/plans/table?billingCycle=${billingCycle}`
|
||||
);
|
||||
|
||||
return data;
|
||||
return data;
|
||||
},
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useGetOrgBillingDetails = (organizationId: string) => {
|
||||
return useQuery({
|
||||
@@ -151,7 +150,7 @@ export const useGetOrgBillingDetails = (organizationId: string) => {
|
||||
},
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useUpdateOrgBillingDetails = () => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -166,7 +165,7 @@ export const useUpdateOrgBillingDetails = () => {
|
||||
email?: string;
|
||||
}) => {
|
||||
const { data } = await apiRequest.patch(
|
||||
`/api/v1/organizations/${organizationId}/billing-details`,
|
||||
`/api/v1/organizations/${organizationId}/billing-details`,
|
||||
{
|
||||
name,
|
||||
email
|
||||
@@ -193,7 +192,7 @@ export const useGetOrgPmtMethods = (organizationId: string) => {
|
||||
},
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useAddOrgPmtMethod = () => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -208,8 +207,10 @@ export const useAddOrgPmtMethod = () => {
|
||||
success_url: string;
|
||||
cancel_url: string;
|
||||
}) => {
|
||||
const { data: { url } } = await apiRequest.post(
|
||||
`/api/v1/organizations/${organizationId}/billing-details/payment-methods`,
|
||||
const {
|
||||
data: { url }
|
||||
} = await apiRequest.post(
|
||||
`/api/v1/organizations/${organizationId}/billing-details/payment-methods`,
|
||||
{
|
||||
success_url,
|
||||
cancel_url
|
||||
@@ -230,7 +231,7 @@ export const useDeleteOrgPmtMethod = () => {
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
organizationId,
|
||||
pmtMethodId,
|
||||
pmtMethodId
|
||||
}: {
|
||||
organizationId: string;
|
||||
pmtMethodId: string;
|
||||
@@ -245,7 +246,7 @@ export const useDeleteOrgPmtMethod = () => {
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgPmtMethods(dto.organizationId));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useGetOrgTaxIds = (organizationId: string) => {
|
||||
return useQuery({
|
||||
@@ -259,7 +260,7 @@ export const useGetOrgTaxIds = (organizationId: string) => {
|
||||
},
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useAddOrgTaxId = () => {
|
||||
const queryClient = useQueryClient();
|
||||
@@ -275,7 +276,7 @@ export const useAddOrgTaxId = () => {
|
||||
value: string;
|
||||
}) => {
|
||||
const { data } = await apiRequest.post(
|
||||
`/api/v1/organizations/${organizationId}/billing-details/tax-ids`,
|
||||
`/api/v1/organizations/${organizationId}/billing-details/tax-ids`,
|
||||
{
|
||||
type,
|
||||
value
|
||||
@@ -294,13 +295,7 @@ export const useDeleteOrgTaxId = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
organizationId,
|
||||
taxId,
|
||||
}: {
|
||||
organizationId: string;
|
||||
taxId: string;
|
||||
}) => {
|
||||
mutationFn: async ({ organizationId, taxId }: { organizationId: string; taxId: string }) => {
|
||||
const { data } = await apiRequest.delete(
|
||||
`/api/v1/organizations/${organizationId}/billing-details/tax-ids/${taxId}`
|
||||
);
|
||||
@@ -311,7 +306,7 @@ export const useDeleteOrgTaxId = () => {
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useGetOrgInvoices = (organizationId: string) => {
|
||||
return useQuery({
|
||||
@@ -325,7 +320,7 @@ export const useGetOrgInvoices = (organizationId: string) => {
|
||||
},
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useCreateCustomerPortalSession = () => {
|
||||
return useMutation({
|
||||
@@ -343,7 +338,7 @@ export const useGetOrgLicenses = (organizationId: string) => {
|
||||
queryKey: organizationKeys.getOrgLicenses(organizationId),
|
||||
queryFn: async () => {
|
||||
if (organizationId === "") return undefined;
|
||||
|
||||
|
||||
const { data } = await apiRequest.get<License[]>(
|
||||
`/api/v1/organizations/${organizationId}/licenses`
|
||||
);
|
||||
@@ -352,18 +347,16 @@ export const useGetOrgLicenses = (organizationId: string) => {
|
||||
},
|
||||
enabled: true
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const useDeleteOrgById = () => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation({
|
||||
mutationFn: async ({
|
||||
organizationId,
|
||||
}: {
|
||||
organizationId: string;
|
||||
}) => {
|
||||
const { data: { organization } } = await apiRequest.delete<{ organization: Organization }>(
|
||||
mutationFn: async ({ organizationId }: { organizationId: string }) => {
|
||||
const {
|
||||
data: { organization }
|
||||
} = await apiRequest.delete<{ organization: Organization }>(
|
||||
`/api/v2/organizations/${organizationId}`
|
||||
);
|
||||
return organization;
|
||||
@@ -372,8 +365,12 @@ export const useDeleteOrgById = () => {
|
||||
queryClient.invalidateQueries(organizationKeys.getUserOrganizations);
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgPlanBillingInfo(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(organizationKeys.getOrgPlansTable(dto.organizationId, "yearly"));
|
||||
queryClient.invalidateQueries(
|
||||
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.getOrgPmtMethods(dto.organizationId));
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgTaxIds(dto.organizationId));
|
||||
@@ -381,4 +378,4 @@ export const useDeleteOrgById = () => {
|
||||
queryClient.invalidateQueries(organizationKeys.getOrgLicenses(dto.organizationId));
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -70,7 +70,9 @@ import {
|
||||
useGetUserAction,
|
||||
useLogoutUser,
|
||||
useRegisterUserAction,
|
||||
useUploadWsKey} from "@app/hooks/api";
|
||||
useUploadWsKey
|
||||
} from "@app/hooks/api";
|
||||
import { CreateOrgModal } from "@app/views/Org/components";
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
@@ -114,12 +116,12 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
// eslint-disable-next-line prefer-const
|
||||
const { workspaces, currentWorkspace } = useWorkspace();
|
||||
const { orgs, currentOrg } = useOrganization();
|
||||
|
||||
|
||||
const { user } = useUser();
|
||||
const { subscription } = useSubscription();
|
||||
const workspaceId = currentWorkspace?._id || "";
|
||||
const { data: updateClosed } = useGetUserAction("september_update_closed");
|
||||
|
||||
|
||||
const { data: secretApprovalReqCount } = useGetSecretApprovalRequestCount({ workspaceId });
|
||||
|
||||
const isAddingProjectsAllowed = subscription?.workspaceLimit
|
||||
@@ -133,7 +135,8 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"addNewWs",
|
||||
"upgradePlan"
|
||||
"upgradePlan",
|
||||
"createOrg"
|
||||
] as const);
|
||||
const {
|
||||
control,
|
||||
@@ -150,7 +153,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
|
||||
const closeUpdate = async () => {
|
||||
await registerUserAction.mutateAsync("september_update_closed");
|
||||
}
|
||||
};
|
||||
|
||||
const logout = useLogoutUser();
|
||||
const logOutUser = async () => {
|
||||
@@ -316,6 +319,22 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
</Button>
|
||||
</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" />
|
||||
<button type="button" onClick={logOutUser} className="w-full">
|
||||
<DropdownMenuItem>Log Out</DropdownMenuItem>
|
||||
@@ -488,7 +507,7 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
>
|
||||
Secret approvals
|
||||
{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}
|
||||
</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-[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={`${!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 className="w-full mt-2 text-md text-mineshaft-100 font-semibold">Infisical September update</div>
|
||||
<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>
|
||||
<div className="h-[6.77rem] w-full rounded-md mt-2 border border-mineshaft-700">
|
||||
<Image src="/images/infisical-update-september-2023.png" height={319} width={539} alt="kubernetes image" className="rounded-sm" />
|
||||
<div
|
||||
className={`${
|
||||
!updateClosed ? "block" : "hidden"
|
||||
} 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`}
|
||||
>
|
||||
<div className="text-md mt-2 w-full font-semibold text-mineshaft-100">
|
||||
Infisical September update
|
||||
</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
|
||||
type="button"
|
||||
onClick={() => closeUpdate()}
|
||||
className="text-mineshaft-400 hover:text-mineshaft-100 duration-200"
|
||||
className="text-mineshaft-400 duration-200 hover:text-mineshaft-100"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
@@ -617,9 +650,10 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
href="https://infisical.com/blog/infisical-update-september-2023"
|
||||
target="_blank"
|
||||
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>
|
||||
</div>
|
||||
</div>
|
||||
@@ -778,6 +812,10 @@ export const AppLayout = ({ children }: LayoutProps) => {
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
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]">
|
||||
{children}
|
||||
</main>
|
||||
|
||||
@@ -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";
|
||||
|
||||
const schema = yup.object({
|
||||
name: yup.string().required("Organization name is required"),
|
||||
}).required();
|
||||
|
||||
export type FormData = yup.InferType<typeof schema>;
|
||||
import { CreateOrgModal } from "../components";
|
||||
|
||||
export const NonePage = () => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
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
|
||||
});
|
||||
|
||||
localStorage.setItem("orgData.id", organization._id);
|
||||
const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const);
|
||||
|
||||
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>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||
<CreateOrgModal
|
||||
isOpen={popUp.createOrg.isOpen}
|
||||
onClose={() => handlePopUpToggle("createOrg", false)}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
104
frontend/src/views/Org/components/CreateOrgModal.tsx
Normal file
104
frontend/src/views/Org/components/CreateOrgModal.tsx
Normal file
@@ -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>
|
||||
);
|
||||
};
|
||||
1
frontend/src/views/Org/components/index.tsx
Normal file
1
frontend/src/views/Org/components/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { CreateOrgModal } from "./CreateOrgModal";
|
||||
Reference in New Issue
Block a user