feat(rbac): implemented granular blocking of actions based on permissions on org level ui

This commit is contained in:
Akhil Mohan
2023-09-08 21:21:43 +05:30
parent 34fb7be1c4
commit aac3168c80
41 changed files with 1992 additions and 1652 deletions
@@ -1,125 +1,148 @@
import { OrgPermissionCan } from "@app/components/permissions";
import { Button } from "@app/components/v2";
import { useOrganization,useSubscription } from "@app/context";
import {
useCreateCustomerPortalSession,
useGetOrgPlanBillingInfo,
useGetOrgTrialUrl
import {
OrgGeneralPermissionActions,
OrgPermissionSubjects,
useOrganization,
useSubscription
} from "@app/context";
import {
useCreateCustomerPortalSession,
useGetOrgPlanBillingInfo,
useGetOrgTrialUrl
} from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
import { ManagePlansModal } from "./ManagePlansModal";
export const PreviewSection = () => {
const { currentOrg } = useOrganization();
const { subscription } = useSubscription();
const { data, isLoading } = useGetOrgPlanBillingInfo(currentOrg?._id ?? "");
const getOrgTrialUrl = useGetOrgTrialUrl();
const createCustomerPortalSession = useCreateCustomerPortalSession();
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
"managePlan"
] as const);
const formatAmount = (amount: number) => {
const formattedTotal = (Math.floor(amount) / 100).toLocaleString("en-US", {
style: "currency",
currency: "USD",
});
return formattedTotal;
}
const formatDate = (date: number) => {
const createdDate = new Date(date * 1000);
const day: number = createdDate.getDate();
const month: number = createdDate.getMonth() + 1;
const year: number = createdDate.getFullYear();
const formattedDate: string = `${day}/${month}/${year}`;
return formattedDate;
}
const { currentOrg } = useOrganization();
const { subscription } = useSubscription();
const { data, isLoading } = useGetOrgPlanBillingInfo(currentOrg?._id ?? "");
const getOrgTrialUrl = useGetOrgTrialUrl();
const createCustomerPortalSession = useCreateCustomerPortalSession();
function formatPlanSlug(slug: string) {
return slug
.replace(/(\b[a-z])/g, match => match.toUpperCase())
.replace(/-/g, " ");
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["managePlan"] as const);
const formatAmount = (amount: number) => {
const formattedTotal = (Math.floor(amount) / 100).toLocaleString("en-US", {
style: "currency",
currency: "USD"
});
return formattedTotal;
};
const formatDate = (date: number) => {
const createdDate = new Date(date * 1000);
const day: number = createdDate.getDate();
const month: number = createdDate.getMonth() + 1;
const year: number = createdDate.getFullYear();
const formattedDate: string = `${day}/${month}/${year}`;
return formattedDate;
};
function formatPlanSlug(slug: string) {
return slug.replace(/(\b[a-z])/g, (match) => match.toUpperCase()).replace(/-/g, " ");
}
const handleUpgradeBtnClick = async () => {
try {
if (!subscription || !currentOrg) return;
if (!subscription.has_used_trial) {
// direct user to start pro trial
const url = await getOrgTrialUrl.mutateAsync({
orgId: currentOrg._id,
success_url: window.location.href
});
window.location.href = url;
} else {
// open compare plans modal
handlePopUpOpen("managePlan");
}
} catch (err) {
console.error(err);
}
const handleUpgradeBtnClick = async () => {
try {
if (!subscription || !currentOrg) return;
if (!subscription.has_used_trial) {
// direct user to start pro trial
const url = await getOrgTrialUrl.mutateAsync({
orgId: currentOrg._id,
success_url: window.location.href
});
window.location.href = url;
} else {
// open compare plans modal
handlePopUpOpen("managePlan");
}
} catch (err) {
console.error(err);
}
}
return (
<div>
{subscription && subscription?.slug !== "enterprise" && subscription?.slug !== "pro" && subscription?.slug !== "pro-annual" && (
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600 mb-6 flex items-center bg-mineshaft-600">
<div className="flex-1">
<h2 className="text-xl font-semibold text-mineshaft-50">Become Infisical</h2>
<p className="text-gray-400 mt-4">Unlimited members, projects, RBAC, smart alerts, and so much more</p>
</div>
<Button
// onClick={() => handlePopUpOpen("managePlan")}
onClick={() => handleUpgradeBtnClick()}
color="mineshaft"
>
{!subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan"}
</Button>
</div>
)}
{!isLoading && subscription && data && (
<div className="flex mb-6">
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 mr-4 border border-mineshaft-600">
<p className="mb-2 text-gray-400">Current plan</p>
<p className="text-2xl text-mineshaft-50 font-semibold mb-8">
{`${formatPlanSlug(subscription.slug)} ${subscription.status === "trialing" ? "(Trial)" : ""}`}
</p>
<button
type="button"
onClick={async () => {
if (!currentOrg?._id) return;
const { url } = await createCustomerPortalSession.mutateAsync(currentOrg._id);
window.location.href = url;
}}
className="text-primary"
>
Manage plan &rarr;
</button>
</div>
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600 mr-4">
<p className="mb-2 text-gray-400">Price</p>
<p className="text-2xl mb-8 text-mineshaft-50 font-semibold">
{subscription.status === "trialing" ? "$0.00 / month" : `${formatAmount(data.amount)} / ${data.interval}`}
</p>
</div>
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600">
<p className="mb-2 text-gray-400">Subscription renews on</p>
<p className="text-2xl mb-8 text-mineshaft-50 font-semibold">
{formatDate(data.currentPeriodEnd)}
</p>
</div>
</div>
)}
<ManagePlansModal
popUp={popUp}
handlePopUpToggle={handlePopUpToggle}
/>
};
return (
<div>
{subscription &&
subscription?.slug !== "enterprise" &&
subscription?.slug !== "pro" &&
subscription?.slug !== "pro-annual" && (
<div className="p-4 rounded-lg flex-1 border border-mineshaft-600 mb-6 flex items-center bg-mineshaft-600">
<div className="flex-1">
<h2 className="text-xl font-semibold text-mineshaft-50">Become Infisical</h2>
<p className="text-gray-400 mt-4">
Unlimited members, projects, RBAC, smart alerts, and so much more
</p>
</div>
<OrgPermissionCan
I={OrgGeneralPermissionActions.Create}
a={OrgPermissionSubjects.Billing}
>
{(isAllowed) => (
<Button
onClick={() => handleUpgradeBtnClick()}
color="mineshaft"
isDisabled={!isAllowed}
>
{!subscription.has_used_trial ? "Start Pro Free Trial" : "Upgrade Plan"}
</Button>
)}
</OrgPermissionCan>
</div>
)}
{!isLoading && subscription && data && (
<div className="flex mb-6">
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 mr-4 border border-mineshaft-600">
<p className="mb-2 text-gray-400">Current plan</p>
<p className="text-2xl text-mineshaft-50 font-semibold mb-8">
{`${formatPlanSlug(subscription.slug)} ${
subscription.status === "trialing" ? "(Trial)" : ""
}`}
</p>
<OrgPermissionCan
I={OrgGeneralPermissionActions.Edit}
a={OrgPermissionSubjects.Billing}
>
{(isAllowed) => (
<button
type="button"
onClick={async () => {
if (!currentOrg?._id) return;
const { url } = await createCustomerPortalSession.mutateAsync(currentOrg._id);
window.location.href = url;
}}
disabled={!isAllowed}
className="text-primary"
>
Manage plan &rarr;
</button>
)}
</OrgPermissionCan>
</div>
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600 mr-4">
<p className="mb-2 text-gray-400">Price</p>
<p className="text-2xl mb-8 text-mineshaft-50 font-semibold">
{subscription.status === "trialing"
? "$0.00 / month"
: `${formatAmount(data.amount)} / ${data.interval}`}
</p>
</div>
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600">
<p className="mb-2 text-gray-400">Subscription renews on</p>
<p className="text-2xl mb-8 text-mineshaft-50 font-semibold">
{formatDate(data.currentPeriodEnd)}
</p>
</div>
</div>
);
}
)}
<ManagePlansModal popUp={popUp} handlePopUpToggle={handlePopUpToggle} />
</div>
);
};
@@ -1,98 +1,92 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
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
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import {
useGetOrgBillingDetails,
useUpdateOrgBillingDetails
} from "@app/hooks/api";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button, FormControl, Input } from "@app/components/v2";
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useGetOrgBillingDetails, useUpdateOrgBillingDetails } from "@app/hooks/api";
const schema = yup.object({
const schema = yup
.object({
name: yup.string().required("Company name is required")
}).required();
})
.required();
export const CompanyNameSection = () => {
const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization();
const { reset, control, handleSubmit } = useForm({
defaultValues: {
name: ""
},
resolver: yupResolver(schema)
});
const { data } = useGetOrgBillingDetails(currentOrg?._id ?? "");
const { mutateAsync, isLoading } = useUpdateOrgBillingDetails();
useEffect(() => {
if (data) {
reset({
name: data?.name ?? ""
});
}
}, [data]);
const onFormSubmit = async ({ name }: { name: string }) => {
try {
if (!currentOrg?._id) return;
if (name === "") return;
await mutateAsync({
name,
organizationId: currentOrg._id
});
createNotification({
text: "Successfully updated business name",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to update business name",
type: "error"
});
}
}
const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization();
const { reset, control, handleSubmit } = useForm({
defaultValues: {
name: ""
},
resolver: yupResolver(schema)
});
const { data } = useGetOrgBillingDetails(currentOrg?._id ?? "");
const { mutateAsync, isLoading } = useUpdateOrgBillingDetails();
return (
<form
onSubmit={handleSubmit(onFormSubmit)}
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
>
<h2 className="text-xl font-semibold flex-1 text-mineshaft-100 mb-8">
Business name
</h2>
<div className="max-w-md">
<Controller
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Input
placeholder="Acme Corp"
{...field}
className="bg-mineshaft-800"
/>
</FormControl>
)}
control={control}
name="name"
/>
</div>
<Button
type="submit"
colorSchema="secondary"
isLoading={isLoading}
isDisabled={isLoading}
>
Save
</Button>
</form>
);
}
useEffect(() => {
if (data) {
reset({
name: data?.name ?? ""
});
}
}, [data]);
const onFormSubmit = async ({ name }: { name: string }) => {
try {
if (!currentOrg?._id) return;
if (name === "") return;
await mutateAsync({
name,
organizationId: currentOrg._id
});
createNotification({
text: "Successfully updated business name",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to update business name",
type: "error"
});
}
};
return (
<form
onSubmit={handleSubmit(onFormSubmit)}
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
>
<h2 className="text-xl font-semibold flex-1 text-mineshaft-100 mb-8">Business name</h2>
<div className="max-w-md">
<Controller
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Input placeholder="Acme Corp" {...field} className="bg-mineshaft-800" />
</FormControl>
)}
control={control}
name="name"
/>
</div>
<OrgPermissionCan I={OrgGeneralPermissionActions.Edit} a={OrgPermissionSubjects.Billing}>
{(isAllowed) => (
<Button
type="submit"
colorSchema="secondary"
isLoading={isLoading}
isDisabled={isLoading || !isAllowed}
>
Save
</Button>
)}
</OrgPermissionCan>
</form>
);
};
@@ -1,97 +1,93 @@
import { useEffect } from "react";
import { Controller, useForm } from "react-hook-form";
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} from "@app/components/v2";
import { useOrganization } from "@app/context";
import {
useGetOrgBillingDetails,
useUpdateOrgBillingDetails
} from "@app/hooks/api";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button, FormControl, Input } from "@app/components/v2";
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useGetOrgBillingDetails, useUpdateOrgBillingDetails } from "@app/hooks/api";
const schema = yup.object({
const schema = yup
.object({
email: yup.string().required("Email is required")
}).required();
})
.required();
export const InvoiceEmailSection = () => {
const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization();
const { reset, control, handleSubmit } = useForm({
defaultValues: {
email: ""
},
resolver: yupResolver(schema)
});
const { data } = useGetOrgBillingDetails(currentOrg?._id ?? "");
const { mutateAsync, isLoading } = useUpdateOrgBillingDetails();
const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization();
const { reset, control, handleSubmit } = useForm({
defaultValues: {
email: ""
},
resolver: yupResolver(schema)
});
const { data } = useGetOrgBillingDetails(currentOrg?._id ?? "");
const { mutateAsync, isLoading } = useUpdateOrgBillingDetails();
useEffect(() => {
if (data) {
reset({
email: data?.email ?? ""
});
}
}, [data]);
const onFormSubmit = async ({ email }: { email: string }) => {
try {
if (!currentOrg?._id) return;
if (email === "") return;
await mutateAsync({
email,
organizationId: currentOrg._id
});
createNotification({
text: "Successfully updated invoice email recipient",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to update invoice email recipient",
type: "error"
});
}
useEffect(() => {
if (data) {
reset({
email: data?.email ?? ""
});
}
}, [data]);
return (
<form
onSubmit={handleSubmit(onFormSubmit)}
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
>
<h2 className="text-xl font-semibold flex-1 text-white mb-8">
Invoice email recipient
</h2>
<div className="max-w-md">
<Controller
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Input
placeholder="[email protected]"
{...field}
className="bg-mineshaft-800"
/>
</FormControl>
)}
control={control}
name="email"
/>
</div>
<Button
type="submit"
colorSchema="secondary"
isLoading={isLoading}
>
Save
</Button>
</form>
);
}
const onFormSubmit = async ({ email }: { email: string }) => {
try {
if (!currentOrg?._id) return;
if (email === "") return;
await mutateAsync({
email,
organizationId: currentOrg._id
});
createNotification({
text: "Successfully updated invoice email recipient",
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: "Failed to update invoice email recipient",
type: "error"
});
}
};
return (
<form
onSubmit={handleSubmit(onFormSubmit)}
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
>
<h2 className="text-xl font-semibold flex-1 text-white mb-8">Invoice email recipient</h2>
<div className="max-w-md">
<Controller
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Input placeholder="[email protected]" {...field} className="bg-mineshaft-800" />
</FormControl>
)}
control={control}
name="email"
/>
</div>
<OrgPermissionCan I={OrgGeneralPermissionActions.Edit} a={OrgPermissionSubjects.Billing}>
{(isAllowed) => (
<Button
type="submit"
colorSchema="secondary"
isLoading={isLoading}
isDisabled={isLoading || !isAllowed}
>
Save
</Button>
)}
</OrgPermissionCan>
</form>
);
};
@@ -1,46 +1,47 @@
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
Button
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button } from "@app/components/v2";
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useAddOrgPmtMethod } from "@app/hooks/api";
import { PmtMethodsTable } from "./PmtMethodsTable";
export const PmtMethodsSection = () => {
const { currentOrg } = useOrganization();
const { mutateAsync, isLoading } = useAddOrgPmtMethod();
const handleAddPmtMethodBtnClick = async () => {
if (!currentOrg?._id) return;
const url = await mutateAsync({
organizationId: currentOrg._id,
success_url: window.location.href,
cancel_url: window.location.href
});
window.location.href = url;
}
return (
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
<div className="flex items-center mb-8">
<h2 className="text-xl font-semibold flex-1 text-white">
Payment methods
</h2>
<Button
onClick={handleAddPmtMethodBtnClick}
colorSchema="secondary"
isLoading={isLoading}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
Add method
</Button>
</div>
<PmtMethodsTable />
</div>
);
}
const { currentOrg } = useOrganization();
const { mutateAsync, isLoading } = useAddOrgPmtMethod();
const handleAddPmtMethodBtnClick = async () => {
if (!currentOrg?._id) return;
const url = await mutateAsync({
organizationId: currentOrg._id,
success_url: window.location.href,
cancel_url: window.location.href
});
window.location.href = url;
};
return (
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
<div className="flex items-center mb-8">
<h2 className="text-xl font-semibold flex-1 text-white">Payment methods</h2>
<OrgPermissionCan I={OrgGeneralPermissionActions.Create} a={OrgPermissionSubjects.Billing}>
{(isAllowed) => (
<Button
onClick={handleAddPmtMethodBtnClick}
colorSchema="secondary"
isLoading={isLoading}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
isDisabled={!isAllowed}
>
Add method
</Button>
)}
</OrgPermissionCan>
</div>
<PmtMethodsTable />
</div>
);
};
@@ -1,6 +1,7 @@
import { faCreditCard, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OrgPermissionCan } from "@app/components/permissions";
import {
EmptyState,
IconButton,
@@ -13,7 +14,7 @@ import {
THead,
Tr
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useDeleteOrgPmtMethod, useGetOrgPmtMethods } from "@app/hooks/api";
export const PmtMethodsTable = () => {
@@ -52,17 +53,25 @@ export const PmtMethodsTable = () => {
<Td>{last4}</Td>
<Td>{`${exp_month}/${exp_year}`}</Td>
<Td>
<IconButton
onClick={async () => {
await handleDeletePmtMethodBtnClick(_id);
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
<OrgPermissionCan
I={OrgGeneralPermissionActions.Delete}
a={OrgPermissionSubjects.Billing}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
{(isAllowed) => (
<IconButton
onClick={async () => {
await handleDeletePmtMethodBtnClick(_id);
}}
size="lg"
isDisabled={!isAllowed}
colorSchema="danger"
variant="plain"
ariaLabel="update"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</OrgPermissionCan>
</Td>
</Tr>
))}
@@ -1,38 +1,42 @@
import { faPlus } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OrgPermissionCan } from "@app/components/permissions";
import { Button } from "@app/components/v2";
import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context";
import { usePopUp } from "@app/hooks/usePopUp";
import { TaxIDModal } from "./TaxIDModal";
import { TaxIDTable } from "./TaxIDTable";
export const TaxIDSection = () => {
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"addTaxID"
] as const);
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"addTaxID"
] as const);
return (
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
<div className="flex items-center mb-8">
<h2 className="text-xl font-semibold flex-1 text-white">
Tax ID
</h2>
<Button
onClick={() => handlePopUpOpen("addTaxID")}
colorSchema="secondary"
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
Add method
</Button>
</div>
<TaxIDTable />
<TaxIDModal
popUp={popUp}
handlePopUpClose={handlePopUpClose}
handlePopUpToggle={handlePopUpToggle}
/>
</div>
);
}
return (
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
<div className="flex items-center mb-8">
<h2 className="text-xl font-semibold flex-1 text-white">Tax ID</h2>
<OrgPermissionCan I={OrgGeneralPermissionActions.Edit} a={OrgPermissionSubjects.Billing}>
{(isAllowed) => (
<Button
onClick={() => handlePopUpOpen("addTaxID")}
colorSchema="secondary"
isDisabled={!isAllowed}
leftIcon={<FontAwesomeIcon icon={faPlus} />}
>
Add method
</Button>
)}
</OrgPermissionCan>
</div>
<TaxIDTable />
<TaxIDModal
popUp={popUp}
handlePopUpClose={handlePopUpClose}
handlePopUpToggle={handlePopUpToggle}
/>
</div>
);
};
@@ -1,6 +1,7 @@
import { faFileInvoice, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { OrgPermissionCan } from "@app/components/permissions";
import {
EmptyState,
IconButton,
@@ -13,7 +14,7 @@ import {
THead,
Tr
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import { OrgGeneralPermissionActions, OrgPermissionSubjects, useOrganization } from "@app/context";
import { useDeleteOrgTaxId, useGetOrgTaxIds } from "@app/hooks/api";
const taxIDTypeLabelMap: { [key: string]: string } = {
@@ -101,17 +102,25 @@ export const TaxIDTable = () => {
<Td>{taxIDTypeLabelMap[type]}</Td>
<Td>{value}</Td>
<Td>
<IconButton
onClick={async () => {
await handleDeleteTaxIdBtnClick(_id);
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
<OrgPermissionCan
I={OrgGeneralPermissionActions.Delete}
a={OrgPermissionSubjects.Billing}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
{(isAllowed) => (
<IconButton
onClick={async () => {
await handleDeleteTaxIdBtnClick(_id);
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
isDisabled={!isAllowed}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
)}
</OrgPermissionCan>
</Td>
</Tr>
))}
@@ -1,5 +1,8 @@
import { Fragment } from "react"
import { Tab } from "@headlessui/react"
import { Fragment } from "react";
import { Tab } from "@headlessui/react";
import { OrgGeneralPermissionActions, OrgPermissionSubjects } from "@app/context";
import { withPermission } from "@app/hoc";
import { BillingCloudTab } from "../BillingCloudTab";
import { BillingDetailsTab } from "../BillingDetailsTab";
@@ -7,43 +10,48 @@ import { BillingReceiptsTab } from "../BillingReceiptsTab";
import { BillingSelfHostedTab } from "../BillingSelfHostedTab";
const tabs = [
{ name: "Infisical Cloud", key: "tab-infisical-cloud" },
{ name: "Infisical Self-Hosted", key: "tab-infisical-self-hosted" },
{ name: "Receipts", key: "tab-receipts" },
{ name: "Billing details", key: "tab-billing-details" }
{ name: "Infisical Cloud", key: "tab-infisical-cloud" },
{ name: "Infisical Self-Hosted", key: "tab-infisical-self-hosted" },
{ name: "Receipts", key: "tab-receipts" },
{ name: "Billing details", key: "tab-billing-details" }
];
export const BillingTabGroup = () => {
export const BillingTabGroup = withPermission(
() => {
return (
<Tab.Group>
<Tab.List className="mt-8 mb-6 border-b-2 border-mineshaft-800">
{tabs.map((tab) => (
<Tab as={Fragment} key={tab.key}>
{({ selected }) => (
<button
type="button"
className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${selected ? "border-b border-white text-white" : "text-mineshaft-400"}`}
>
{tab.name}
</button>
)}
</Tab>
))}
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<BillingCloudTab />
</Tab.Panel>
<Tab.Panel>
<BillingSelfHostedTab />
</Tab.Panel>
<Tab.Panel>
<BillingReceiptsTab />
</Tab.Panel>
<Tab.Panel>
<BillingDetailsTab />
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
<Tab.Group>
<Tab.List className="mt-8 mb-6 border-b-2 border-mineshaft-800">
{tabs.map((tab) => (
<Tab as={Fragment} key={tab.key}>
{({ selected }) => (
<button
type="button"
className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${
selected ? "border-b border-white text-white" : "text-mineshaft-400"
}`}
>
{tab.name}
</button>
)}
</Tab>
))}
</Tab.List>
<Tab.Panels>
<Tab.Panel>
<BillingCloudTab />
</Tab.Panel>
<Tab.Panel>
<BillingSelfHostedTab />
</Tab.Panel>
<Tab.Panel>
<BillingReceiptsTab />
</Tab.Panel>
<Tab.Panel>
<BillingDetailsTab />
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
);
}
},
{ action: OrgGeneralPermissionActions.Read, subject: OrgPermissionSubjects.Billing }
);