feat(infisical-pg): changed frontend api types _id to normal id

This commit is contained in:
Akhil Mohan
2024-01-27 12:38:22 +05:30
parent f3b37de3f3
commit aaba4a0895
161 changed files with 5358 additions and 4510 deletions
@@ -71,7 +71,7 @@ export const PreviewSection = () => {
console.error(err);
}
};
return (
<div>
{subscription &&
@@ -79,10 +79,15 @@ export const PreviewSection = () => {
subscription?.slug !== "pro" &&
subscription?.slug !== "pro-annual" && (
<div className="flex flex-row space-x-6">
<div className="p-4 rounded-lg flex-1 border border-primary/40 mb-6 flex items-center bg-primary/10">
<div className="mb-6 flex flex-1 items-center rounded-lg border border-primary/40 bg-primary/10 p-4">
<div className="flex-1">
<h2 className="text-xl font-medium text-mineshaft-100">Unleash the full power of <span className="text-transparent font-semibold bg-clip-text bg-gradient-to-r from-primary-500 to-yellow">Infisical</span></h2>
<p className="text-gray-400 mt-4">
<h2 className="text-xl font-medium text-mineshaft-100">
Unleash the full power of{" "}
<span className="bg-gradient-to-r from-primary-500 to-yellow bg-clip-text font-semibold text-transparent">
Infisical
</span>
</h2>
<p className="mt-4 text-gray-400">
Get unlimited members, projects, RBAC, smart alerts, and so much more.
</p>
</div>
@@ -98,23 +103,29 @@ export const PreviewSection = () => {
)}
</OrgPermissionCan>
</div>
<div className="flex flex-col max-w-[12rem] w-full items-start border border-mineshaft-600 mb-6 flex items-center bg-mineshaft-800 p-4 rounded-lg">
<div className="mb-4 flex justify-center w-full font-semibold text-mineshaft-200">Want to learn more? </div>
<div className="flex justify-center w-full">
<div className="mb-6 flex flex w-full max-w-[12rem] flex-col items-start items-center rounded-lg border border-mineshaft-600 bg-mineshaft-800 p-4">
<div className="mb-4 flex w-full justify-center font-semibold text-mineshaft-200">
Want to learn more?{" "}
</div>
<div className="flex w-full justify-center">
<Link href="https://infisical.com/schedule-demo">
<span className="rounded-full px-4 py-2 bg-mineshaft-600 border border-mineshaft-500 hover:bg-primary/10 hover:border-primary/40 duration-200 cursor-pointer">
Book a demo <FontAwesomeIcon icon={faArrowUpRightFromSquare} className="text-xs mb-[0.06rem] ml-1"/>
<span className="cursor-pointer rounded-full border border-mineshaft-500 bg-mineshaft-600 px-4 py-2 duration-200 hover:border-primary/40 hover:bg-primary/10">
Book a demo{" "}
<FontAwesomeIcon
icon={faArrowUpRightFromSquare}
className="mb-[0.06rem] ml-1 text-xs"
/>
</span>
</Link>
</Link>
</div>
</div>
</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">
<div className="mb-6 flex">
<div className="mr-4 flex-1 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-2 text-gray-400">Current plan</p>
<p className="text-2xl text-mineshaft-50 font-semibold mb-8">
<p className="mb-8 text-2xl font-semibold text-mineshaft-50">
{`${formatPlanSlug(subscription.slug)} ${
subscription.status === "trialing" ? "(Trial)" : ""
}`}
@@ -136,17 +147,17 @@ export const PreviewSection = () => {
)}
</OrgPermissionCan>
</div>
<div className="p-4 bg-mineshaft-900 rounded-lg flex-1 border border-mineshaft-600 mr-4">
<div className="mr-4 flex-1 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-2 text-gray-400">Price</p>
<p className="text-2xl mb-8 text-mineshaft-50 font-semibold">
<p className="mb-8 text-2xl font-semibold text-mineshaft-50">
{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">
<div className="flex-1 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-2 text-gray-400">Subscription renews on</p>
<p className="text-2xl mb-8 text-mineshaft-50 font-semibold">
<p className="mb-8 text-2xl font-semibold text-mineshaft-50">
{formatDate(data.currentPeriodEnd)}
</p>
</div>
@@ -24,7 +24,7 @@ export const CompanyNameSection = () => {
},
resolver: yupResolver(schema)
});
const { data } = useGetOrgBillingDetails(currentOrg?._id ?? "");
const { data } = useGetOrgBillingDetails(currentOrg?.id ?? "");
const { mutateAsync, isLoading } = useUpdateOrgBillingDetails();
useEffect(() => {
@@ -37,11 +37,11 @@ export const CompanyNameSection = () => {
const onFormSubmit = async ({ name }: { name: string }) => {
try {
if (!currentOrg?._id) return;
if (!currentOrg?.id) return;
if (name === "") return;
await mutateAsync({
name,
organizationId: currentOrg._id
organizationId: currentOrg.id
});
createNotification({
@@ -60,9 +60,9 @@ export const CompanyNameSection = () => {
return (
<form
onSubmit={handleSubmit(onFormSubmit)}
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
>
<h2 className="text-xl font-semibold flex-1 text-mineshaft-100 mb-8">Business name</h2>
<h2 className="mb-8 flex-1 text-xl font-semibold text-mineshaft-100">Business name</h2>
<div className="max-w-md">
<Controller
defaultValue=""
@@ -24,7 +24,7 @@ export const InvoiceEmailSection = () => {
},
resolver: yupResolver(schema)
});
const { data } = useGetOrgBillingDetails(currentOrg?._id ?? "");
const { data } = useGetOrgBillingDetails(currentOrg?.id ?? "");
const { mutateAsync, isLoading } = useUpdateOrgBillingDetails();
useEffect(() => {
@@ -37,12 +37,12 @@ export const InvoiceEmailSection = () => {
const onFormSubmit = async ({ email }: { email: string }) => {
try {
if (!currentOrg?._id) return;
if (!currentOrg?.id) return;
if (email === "") return;
await mutateAsync({
email,
organizationId: currentOrg._id
organizationId: currentOrg.id
});
createNotification({
@@ -61,9 +61,9 @@ export const InvoiceEmailSection = () => {
return (
<form
onSubmit={handleSubmit(onFormSubmit)}
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
>
<h2 className="text-xl font-semibold flex-1 text-white mb-8">Invoice email recipient</h2>
<h2 className="mb-8 flex-1 text-xl font-semibold text-white">Invoice email recipient</h2>
<div className="max-w-md">
<Controller
defaultValue=""
@@ -13,9 +13,9 @@ export const PmtMethodsSection = () => {
const { mutateAsync, isLoading } = useAddOrgPmtMethod();
const handleAddPmtMethodBtnClick = async () => {
if (!currentOrg?._id) return;
if (!currentOrg?.id) return;
const url = await mutateAsync({
organizationId: currentOrg._id,
organizationId: currentOrg.id,
success_url: window.location.href,
cancel_url: window.location.href
});
@@ -24,9 +24,9 @@ export const PmtMethodsSection = () => {
};
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>
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-8 flex items-center">
<h2 className="flex-1 text-xl font-semibold text-white">Payment methods</h2>
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Billing}>
{(isAllowed) => (
<Button
@@ -19,13 +19,13 @@ import { useDeleteOrgPmtMethod, useGetOrgPmtMethods } from "@app/hooks/api";
export const PmtMethodsTable = () => {
const { currentOrg } = useOrganization();
const { data, isLoading } = useGetOrgPmtMethods(currentOrg?._id ?? "");
const { data, isLoading } = useGetOrgPmtMethods(currentOrg?.id ?? "");
const deleteOrgPmtMethod = useDeleteOrgPmtMethod();
const handleDeletePmtMethodBtnClick = async (pmtMethodId: string) => {
if (!currentOrg?._id) return;
if (!currentOrg?.id) return;
await deleteOrgPmtMethod.mutateAsync({
organizationId: currentOrg._id,
organizationId: currentOrg.id,
pmtMethodId
});
};
@@ -46,8 +46,8 @@ export const PmtMethodsTable = () => {
{!isLoading &&
data &&
data?.length > 0 &&
data.map(({ _id, brand, exp_month, exp_year, funding, last4 }) => (
<Tr key={`pmt-method-${_id}`} className="h-10">
data.map(({ id, brand, exp_month, exp_year, funding, last4 }) => (
<Tr key={`pmt-method-${id}`} className="h-10">
<Td>{brand.charAt(0).toUpperCase() + brand.slice(1)}</Td>
<Td>{funding.charAt(0).toUpperCase() + funding.slice(1)}</Td>
<Td>{last4}</Td>
@@ -60,7 +60,7 @@ export const PmtMethodsTable = () => {
{(isAllowed) => (
<IconButton
onClick={async () => {
await handleDeletePmtMethodBtnClick(_id);
await handleDeletePmtMethodBtnClick(id);
}}
size="lg"
isDisabled={!isAllowed}
@@ -1,4 +1,4 @@
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";
@@ -69,10 +69,12 @@ const taxIDTypes = [
{ label: "South Africa VAT", value: "za_vat" }
];
const schema = yup.object({
const schema = yup
.object({
type: yup.string().required("Tax ID type is required"),
value: yup.string().required("Tax ID value is required")
}).required();
})
.required();
export type AddTaxIDFormData = yup.InferType<typeof schema>;
@@ -82,115 +84,100 @@ type Props = {
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addTaxID"]>, state?: boolean) => void;
};
export const TaxIDModal = ({
popUp,
handlePopUpClose,
handlePopUpToggle
}: Props) => {
const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization();
const addOrgTaxId = useAddOrgTaxId();
export const TaxIDModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
const { createNotification } = useNotificationContext();
const { currentOrg } = useOrganization();
const addOrgTaxId = useAddOrgTaxId();
const {
control,
handleSubmit,
reset,
formState: { isSubmitting }
} = useForm<AddTaxIDFormData>({
resolver: yupResolver(schema)
});
const {
control,
handleSubmit,
reset,
formState: { isSubmitting }
} = useForm<AddTaxIDFormData>({
resolver: yupResolver(schema)
});
const onTaxIDModalSubmit = async ({ type, value }: AddTaxIDFormData) => {
try {
if (!currentOrg?._id) return;
await addOrgTaxId.mutateAsync({
organizationId: currentOrg._id,
type,
value
});
const onTaxIDModalSubmit = async ({ type, value }: AddTaxIDFormData) => {
try {
if (!currentOrg?.id) return;
await addOrgTaxId.mutateAsync({
organizationId: currentOrg.id,
type,
value
});
createNotification({
text: "Successfully added Tax ID",
type: "success"
});
handlePopUpClose("addTaxID");
} catch (err) {
console.error(err);
createNotification({
text: "Failed to add Tax ID",
type: "error"
});
}
createNotification({
text: "Successfully added Tax ID",
type: "success"
});
handlePopUpClose("addTaxID");
} catch (err) {
console.error(err);
createNotification({
text: "Failed to add Tax ID",
type: "error"
});
}
};
return (
<Modal
isOpen={popUp?.addTaxID?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("addTaxID", isOpen);
reset();
}}
>
<ModalContent title="Add Tax ID">
<form onSubmit={handleSubmit(onTaxIDModalSubmit)}>
<Controller
control={control}
name="type"
defaultValue="eu_vat"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Type"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{taxIDTypes.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}>
{label}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="value"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Value"
isError={Boolean(error)}
errorText={error?.message}
>
<Input
{...field}
placeholder="DE000000000"
/>
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
return (
<Modal
isOpen={popUp?.addTaxID?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("addTaxID", isOpen);
reset();
}}
>
<ModalContent title="Add Tax ID">
<form onSubmit={handleSubmit(onTaxIDModalSubmit)}>
<Controller
control={control}
name="type"
defaultValue="eu_vat"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl label="Type" errorText={error?.message} isError={Boolean(error)}>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
Add
</Button>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
}
{taxIDTypes.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}>
{label}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="value"
render={({ field, fieldState: { error } }) => (
<FormControl label="Value" isError={Boolean(error)} errorText={error?.message}>
<Input {...field} placeholder="DE000000000" />
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isSubmitting}
isDisabled={isSubmitting}
>
Add
</Button>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
};
@@ -72,13 +72,13 @@ const taxIDTypeLabelMap: { [key: string]: string } = {
export const TaxIDTable = () => {
const { currentOrg } = useOrganization();
const { data, isLoading } = useGetOrgTaxIds(currentOrg?._id ?? "");
const { data, isLoading } = useGetOrgTaxIds(currentOrg?.id ?? "");
const deleteOrgTaxId = useDeleteOrgTaxId();
const handleDeleteTaxIdBtnClick = async (taxId: string) => {
if (!currentOrg?._id) return;
if (!currentOrg?.id) return;
await deleteOrgTaxId.mutateAsync({
organizationId: currentOrg._id,
organizationId: currentOrg.id,
taxId
});
};
@@ -97,8 +97,8 @@ export const TaxIDTable = () => {
{!isLoading &&
data &&
data?.length > 0 &&
data.map(({ _id, type, value }) => (
<Tr key={`tax-id-${_id}`} className="h-10">
data.map(({ id, type, value }) => (
<Tr key={`tax-id-${id}`} className="h-10">
<Td>{taxIDTypeLabelMap[type]}</Td>
<Td>{value}</Td>
<Td>
@@ -109,7 +109,7 @@ export const TaxIDTable = () => {
{(isAllowed) => (
<IconButton
onClick={async () => {
await handleDeleteTaxIdBtnClick(_id);
await handleDeleteTaxIdBtnClick(id);
}}
size="lg"
colorSchema="danger"
@@ -18,7 +18,7 @@ import { useGetOrgInvoices } from "@app/hooks/api";
export const InvoicesTable = () => {
const { currentOrg } = useOrganization();
const { data, isLoading } = useGetOrgInvoices(currentOrg?._id ?? "");
const { data, isLoading } = useGetOrgInvoices(currentOrg?.id ?? "");
return (
<TableContainer className="mt-8">
<Table>
@@ -35,7 +35,7 @@ export const InvoicesTable = () => {
{!isLoading &&
data &&
data?.length > 0 &&
data.map(({ _id, created, paid, number, total, invoice_pdf }) => {
data.map(({ id, created, paid, number, total, invoice_pdf }) => {
const formattedTotal = (Math.floor(total) / 100).toLocaleString("en-US", {
style: "currency",
currency: "USD"
@@ -47,7 +47,7 @@ export const InvoicesTable = () => {
const formattedDate: string = `${day}/${month}/${year}`;
return (
<Tr key={`invoice-${_id}`} className="h-10">
<Tr key={`invoice-${id}`} className="h-10">
<Td>{number}</Td>
<Td>{formattedDate}</Td>
<Td>{paid ? "Paid" : "Not Paid"}</Td>
@@ -12,57 +12,52 @@ import {
Tr
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import {
useGetOrgLicenses
} from "@app/hooks/api";
import { useGetOrgLicenses } from "@app/hooks/api";
export const LicensesSection = () => {
const { currentOrg } = useOrganization();
const { data, isLoading } = useGetOrgLicenses(currentOrg?.id ?? "");
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<h2 className="mb-8 flex-1 text-xl font-semibold text-white">Enterprise licenses</h2>
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Th>License Key</Th>
<Th>Status</Th>
<Th>Issued Date</Th>
<Th>Expiry Date</Th>
</Tr>
</THead>
<TBody>
{!isLoading && data && data?.length > 0 && data.map(({
id,
licenseKey,
isActivated,
createdAt,
expiresAt
}) => {
const formattedCreatedAt = new Date(createdAt).toISOString().split("T")[0];
const formattedExpiresAt = new Date(expiresAt).toISOString().split("T")[0];
return (
<Tr key={`license-${id}`} className="h-10">
<Td>{licenseKey}</Td>
<Td>{isActivated ? "Active" : "Inactive"}</Td>
<Td>{formattedCreatedAt}</Td>
<Td>{formattedExpiresAt}</Td>
</Tr>
);
})}
{isLoading && <TableSkeleton columns={4} innerKey="licenses" />}
{!isLoading && data && data?.length === 0 && (
<Tr>
<Td colSpan={4}>
<EmptyState title="No enterprise licenses on file" icon={faFileContract} />
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
</div>
);
}
const { currentOrg } = useOrganization();
const { data, isLoading } = useGetOrgLicenses(currentOrg?.id ?? "");
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<h2 className="mb-8 flex-1 text-xl font-semibold text-white">Enterprise licenses</h2>
<TableContainer className="mt-4">
<Table>
<THead>
<Tr>
<Th>License Key</Th>
<Th>Status</Th>
<Th>Issued Date</Th>
<Th>Expiry Date</Th>
</Tr>
</THead>
<TBody>
{!isLoading &&
data &&
data?.length > 0 &&
data.map(({ id, licenseKey, isActivated, createdAt, expiresAt }) => {
const formattedCreatedAt = new Date(createdAt).toISOString().split("T")[0];
const formattedExpiresAt = new Date(expiresAt).toISOString().split("T")[0];
return (
<Tr key={`license-${id}`} className="h-10">
<Td>{licenseKey}</Td>
<Td>{isActivated ? "Active" : "Inactive"}</Td>
<Td>{formattedCreatedAt}</Td>
<Td>{formattedExpiresAt}</Td>
</Tr>
);
})}
{isLoading && <TableSkeleton columns={4} innerKey="licenses" />}
{!isLoading && data && data?.length === 0 && (
<Tr>
<Td colSpan={4}>
<EmptyState title="No enterprise licenses on file" icon={faFileContract} />
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
</div>
);
};
@@ -82,9 +82,9 @@ export const OrgSSOSection = (): JSX.Element => {
};
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">SAML SSO Configuration</h2>
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-8 flex items-center">
<h2 className="flex-1 text-xl font-semibold text-white">SAML SSO Configuration</h2>
{!isLoading && (
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Sso}>
{(isAllowed) => (
@@ -117,24 +117,24 @@ export const OrgSSOSection = (): JSX.Element => {
</div>
)}
<div className="mb-4">
<h3 className="text-mineshaft-400 text-sm">SSO identifier</h3>
<p className="text-gray-400 text-md">{data && data.id !== "" ? data.id : "-"}</p>
<h3 className="text-sm text-mineshaft-400">SSO identifier</h3>
<p className="text-md text-gray-400">{data && data.id !== "" ? data.id : "-"}</p>
</div>
<div className="mb-4">
<h3 className="text-mineshaft-400 text-sm">Type</h3>
<p className="text-gray-400 text-md">
<h3 className="text-sm text-mineshaft-400">Type</h3>
<p className="text-md text-gray-400">
{data && data.authProvider !== "" ? ssoAuthProviderMap[data.authProvider] : "-"}
</p>
</div>
<div className="mb-4">
<h3 className="text-mineshaft-400 text-sm">Entrypoint</h3>
<p className="text-gray-400 text-md">
<h3 className="text-sm text-mineshaft-400">Entrypoint</h3>
<p className="text-md text-gray-400">
{data && data.entryPoint !== "" ? data.entryPoint : "-"}
</p>
</div>
<div className="mb-4">
<h3 className="text-mineshaft-400 text-sm">Issuer</h3>
<p className="text-gray-400 text-md">{data && data.issuer !== "" ? data.issuer : "-"}</p>
<h3 className="text-sm text-mineshaft-400">Issuer</h3>
<p className="text-md text-gray-400">{data && data.issuer !== "" ? data.issuer : "-"}</p>
</div>
<SSOModal
popUp={popUp}
@@ -1,44 +1,43 @@
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,
Modal,
ModalContent,
Select,
SelectItem,
TextArea} from "@app/components/v2";
Button,
FormControl,
Input,
Modal,
ModalContent,
Select,
SelectItem,
TextArea
} from "@app/components/v2";
import { useOrganization } from "@app/context";
import {
useCreateSSOConfig,
useGetSSOConfig,
useUpdateSSOConfig
} from "@app/hooks/api";
import { useCreateSSOConfig, useGetSSOConfig, useUpdateSSOConfig } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
enum AuthProvider {
OKTA_SAML = "okta-saml",
AZURE_SAML = "azure-saml",
JUMPCLOUD_SAML = "jumpcloud-saml"
OKTA_SAML = "okta-saml",
AZURE_SAML = "azure-saml",
JUMPCLOUD_SAML = "jumpcloud-saml"
}
const ssoAuthProviders = [
{ label: "Okta SAML", value: AuthProvider.OKTA_SAML },
{ label: "Azure SAML", value: AuthProvider.AZURE_SAML },
{ label: "JumpCloud SAML", value: AuthProvider.JUMPCLOUD_SAML }
{ label: "Okta SAML", value: AuthProvider.OKTA_SAML },
{ label: "Azure SAML", value: AuthProvider.AZURE_SAML },
{ label: "JumpCloud SAML", value: AuthProvider.JUMPCLOUD_SAML }
];
const schema = yup.object({
const schema = yup
.object({
authProvider: yup.string().required("SSO Type is required"),
entryPoint: yup.string().required("IdP entrypoint is required"),
issuer: yup.string().required("Issuer string is required"),
cert: yup.string().required("IdP's public signing certificate is required")
}).required();
})
.required();
export type AddSSOFormData = yup.InferType<typeof schema>;
@@ -48,243 +47,221 @@ type Props = {
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addSSO"]>, state?: boolean) => void;
};
export const SSOModal = ({
popUp,
handlePopUpClose,
handlePopUpToggle
}: Props) => {
const { currentOrg } = useOrganization();
const { createNotification } = useNotificationContext();
const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateSSOConfig();
const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateSSOConfig();
const { data } = useGetSSOConfig(currentOrg?.id ?? "");
const {
control,
handleSubmit,
reset,
watch,
} = useForm<AddSSOFormData>({
defaultValues: {
authProvider: AuthProvider.OKTA_SAML
},
resolver: yupResolver(schema)
});
useEffect(() => {
if (data) {
reset({
authProvider: data?.authProvider ?? "",
entryPoint: data?.entryPoint ?? "",
issuer: data?.issuer ?? "",
cert: data?.cert ?? ""
});
}
}, [data]);
const onSSOModalSubmit = async ({
authProvider,
entryPoint,
issuer,
cert
}: AddSSOFormData) => {
try {
if (!currentOrg) return;
if (!data) {
await createMutateAsync({
organizationId: currentOrg.id,
authProvider,
isActive: false,
entryPoint,
issuer,
cert
});
} else {
await updateMutateAsync({
organizationId: currentOrg.id,
authProvider,
isActive: false,
entryPoint,
issuer,
cert
});
}
export const SSOModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
const { currentOrg } = useOrganization();
const { createNotification } = useNotificationContext();
const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateSSOConfig();
const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateSSOConfig();
const { data } = useGetSSOConfig(currentOrg?.id ?? "");
handlePopUpClose("addSSO");
const { control, handleSubmit, reset, watch } = useForm<AddSSOFormData>({
defaultValues: {
authProvider: AuthProvider.OKTA_SAML
},
resolver: yupResolver(schema)
});
createNotification({
text: `Successfully ${!data ? "added" : "updated"} SAML SSO configuration`,
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: `Failed to ${!data ? "add" : "update"} SAML SSO configuration`,
type: "error"
});
}
useEffect(() => {
if (data) {
reset({
authProvider: data?.authProvider ?? "",
entryPoint: data?.entryPoint ?? "",
issuer: data?.issuer ?? "",
cert: data?.cert ?? ""
});
}
}, [data]);
const renderLabels = (authProvider: string) => {
switch (authProvider){
case AuthProvider.OKTA_SAML:
return ({
acsUrl: "Single sign-on URL",
entityId: "Audience URI (SP Entity ID)",
entryPoint: "Identity Provider Single Sign-On URL",
entryPointPlaceholder: "https://your-domain.okta.com/app/app-name/xxx/sso/saml",
issuer: "Identity Provider Issuer",
issuerPlaceholder: "http://www.okta.com/xxx"
});
case AuthProvider.AZURE_SAML:
return ({
acsUrl: "Reply URL (Assertion Consumer Service URL)",
entityId: "Identifier (Entity ID)",
entryPoint: "Login URL",
entryPointPlaceholder: "https://login.microsoftonline.com/xxx/saml2",
issuer: "Azure Application ID",
issuerPlaceholder: "abc-def-ghi-jkl-mno"
});
case AuthProvider.JUMPCLOUD_SAML:
return ({
acsUrl: "ACS URL",
entityId: "SP Entity ID",
entryPoint: "IDP URL",
entryPointPlaceholder: "https://sso.jumpcloud.com/saml2/xxx",
issuer: "IdP Entity ID",
issuerPlaceholder: "xxx"
});
default:
return ({
acsUrl: "ACS URL",
entityId: "Entity ID",
entryPoint: "Entrypoint",
entryPointPlaceholder: "Enter entrypoint...",
issuer: "Issuer",
issuerPlaceholder: "Enter placeholder..."
});
}
const onSSOModalSubmit = async ({ authProvider, entryPoint, issuer, cert }: AddSSOFormData) => {
try {
if (!currentOrg) return;
if (!data) {
await createMutateAsync({
organizationId: currentOrg.id,
authProvider,
isActive: false,
entryPoint,
issuer,
cert
});
} else {
await updateMutateAsync({
organizationId: currentOrg.id,
authProvider,
isActive: false,
entryPoint,
issuer,
cert
});
}
handlePopUpClose("addSSO");
createNotification({
text: `Successfully ${!data ? "added" : "updated"} SAML SSO configuration`,
type: "success"
});
} catch (err) {
console.error(err);
createNotification({
text: `Failed to ${!data ? "add" : "update"} SAML SSO configuration`,
type: "error"
});
}
const authProvider = watch("authProvider");
};
return (
<Modal
isOpen={popUp?.addSSO?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("addSSO", isOpen);
reset();
}}
>
<ModalContent title="Add SSO">
<form onSubmit={handleSubmit(onSSOModalSubmit)}>
<Controller
control={control}
name="authProvider"
defaultValue="okta-saml"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl
label="Type"
errorText={error?.message}
isError={Boolean(error)}
>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{ssoAuthProviders.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}>
{label}
</SelectItem>
))}
</Select>
</FormControl>
)}
const renderLabels = (authProvider: string) => {
switch (authProvider) {
case AuthProvider.OKTA_SAML:
return {
acsUrl: "Single sign-on URL",
entityId: "Audience URI (SP Entity ID)",
entryPoint: "Identity Provider Single Sign-On URL",
entryPointPlaceholder: "https://your-domain.okta.com/app/app-name/xxx/sso/saml",
issuer: "Identity Provider Issuer",
issuerPlaceholder: "http://www.okta.com/xxx"
};
case AuthProvider.AZURE_SAML:
return {
acsUrl: "Reply URL (Assertion Consumer Service URL)",
entityId: "Identifier (Entity ID)",
entryPoint: "Login URL",
entryPointPlaceholder: "https://login.microsoftonline.com/xxx/saml2",
issuer: "Azure Application ID",
issuerPlaceholder: "abc-def-ghi-jkl-mno"
};
case AuthProvider.JUMPCLOUD_SAML:
return {
acsUrl: "ACS URL",
entityId: "SP Entity ID",
entryPoint: "IDP URL",
entryPointPlaceholder: "https://sso.jumpcloud.com/saml2/xxx",
issuer: "IdP Entity ID",
issuerPlaceholder: "xxx"
};
default:
return {
acsUrl: "ACS URL",
entityId: "Entity ID",
entryPoint: "Entrypoint",
entryPointPlaceholder: "Enter entrypoint...",
issuer: "Issuer",
issuerPlaceholder: "Enter placeholder..."
};
}
};
const authProvider = watch("authProvider");
return (
<Modal
isOpen={popUp?.addSSO?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("addSSO", isOpen);
reset();
}}
>
<ModalContent title="Add SSO">
<form onSubmit={handleSubmit(onSSOModalSubmit)}>
<Controller
control={control}
name="authProvider"
defaultValue="okta-saml"
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
<FormControl label="Type" errorText={error?.message} isError={Boolean(error)}>
<Select
defaultValue={field.value}
{...field}
onValueChange={(e) => onChange(e)}
className="w-full"
>
{ssoAuthProviders.map(({ label, value }) => (
<SelectItem value={String(value || "")} key={label}>
{label}
</SelectItem>
))}
</Select>
</FormControl>
)}
/>
{authProvider && data && (
<>
<div className="mb-4">
<h3 className="text-sm text-mineshaft-400">{renderLabels(authProvider).acsUrl}</h3>
<p className="text-md break-all text-gray-400">{`${window.origin}/api/v1/sso/saml2/${data._id}`}</p>
</div>
<div className="mb-4">
<h3 className="text-sm text-mineshaft-400">
{renderLabels(authProvider).entityId}
</h3>
<p className="text-md text-gray-400">{window.origin}</p>
</div>
<Controller
control={control}
name="entryPoint"
render={({ field, fieldState: { error } }) => (
<FormControl
label={renderLabels(authProvider).entryPoint}
errorText={error?.message}
isError={Boolean(error)}
>
<Input
{...field}
placeholder={renderLabels(authProvider).entryPointPlaceholder}
/>
{authProvider && data && (
<>
<div className="mb-4">
<h3 className="text-mineshaft-400 text-sm">{renderLabels(authProvider).acsUrl}</h3>
<p className="text-gray-400 text-md break-all">{`${window.origin}/api/v1/sso/saml2/${data.id}`}</p>
</div>
<div className="mb-4">
<h3 className="text-mineshaft-400 text-sm">{renderLabels(authProvider).entityId}</h3>
<p className="text-gray-400 text-md">{window.origin}</p>
</div>
<Controller
control={control}
name="entryPoint"
render={({ field, fieldState: { error } }) => (
<FormControl
label={renderLabels(authProvider).entryPoint}
errorText={error?.message}
isError={Boolean(error)}
>
<Input
{...field}
placeholder={renderLabels(authProvider).entryPointPlaceholder}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="issuer"
render={({ field, fieldState: { error } }) => (
<FormControl
label={renderLabels(authProvider).issuer}
errorText={error?.message}
isError={Boolean(error)}
>
<Input
{...field}
placeholder={renderLabels(authProvider).issuerPlaceholder}
/>
</FormControl>
)}
/>
<Controller
control={control}
name="cert"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Certificate"
errorText={error?.message}
isError={Boolean(error)}
>
<TextArea
{...field}
placeholder="-----BEGIN CERTIFICATE----- ..."
/>
</FormControl>
)}
/>
</>
)}
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={createIsLoading || updateIsLoading}
>
{!data ? "Add" : "Update"}
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpClose("addSSO")}
>
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
}
</FormControl>
)}
/>
<Controller
control={control}
name="issuer"
render={({ field, fieldState: { error } }) => (
<FormControl
label={renderLabels(authProvider).issuer}
errorText={error?.message}
isError={Boolean(error)}
>
<Input {...field} placeholder={renderLabels(authProvider).issuerPlaceholder} />
</FormControl>
)}
/>
<Controller
control={control}
name="cert"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Certificate"
errorText={error?.message}
isError={Boolean(error)}
>
<TextArea {...field} placeholder="-----BEGIN CERTIFICATE----- ..." />
</FormControl>
)}
/>
</>
)}
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={createIsLoading || updateIsLoading}
>
{!data ? "Add" : "Update"}
</Button>
<Button
colorSchema="secondary"
variant="plain"
onClick={() => handlePopUpClose("addSSO")}
>
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
};
@@ -45,8 +45,8 @@ export const OrgDeleteSection = () => {
};
return (
<div className="p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600 mb-6">
<p className="text-xl font-semibold text-mineshaft-100 mb-4">Danger Zone</p>
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Danger Zone</p>
<Button
isLoading={isLoading}
colorSchema="danger"
@@ -51,9 +51,9 @@ export const OrgNameChangeSection = (): JSX.Element => {
return (
<form
onSubmit={handleSubmit(onFormSubmit)}
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
>
<p className="text-xl font-semibold text-mineshaft-100 mb-4">Name</p>
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Name</p>
<div className="mb-2 max-w-md">
<Controller
defaultValue=""
@@ -55,9 +55,9 @@ export const APIKeyTable = () => {
{!isLoading &&
data &&
data.length > 0 &&
data.map(({ _id, name, createdAt, expiresAt, lastUsed }) => {
data.map(({ id, name, createdAt, expiresAt, lastUsed }) => {
return (
<Tr className="h-10" key={`api-key-${_id}`}>
<Tr className="h-10" key={`api-key-${id}`}>
<Td>{name}</Td>
<Td>{format(new Date(lastUsed), "yyyy-MM-dd")}</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
@@ -65,7 +65,7 @@ export const APIKeyTable = () => {
<Td>
<IconButton
onClick={async () => {
await handleDeleteAPIKeyDataClick(_id);
await handleDeleteAPIKeyDataClick(id);
}}
size="lg"
colorSchema="danger"
@@ -3,105 +3,96 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { format } from "date-fns";
import {
EmptyState,
IconButton,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
EmptyState,
IconButton,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import {
useGetMyAPIKeysV2
} from "@app/hooks/api";
import { useGetMyAPIKeysV2 } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["deleteAPIKeyV2", "apiKeyV2"]>,
data?: {
apiKeyDataId?: string;
name?: string;
}
) => void;
};
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["deleteAPIKeyV2", "apiKeyV2"]>,
data?: {
apiKeyDataId?: string;
name?: string;
}
) => void;
};
export const APIKeyV2Table = ({
handlePopUpOpen
}: Props) => {
const { data, isLoading } = useGetMyAPIKeysV2();
return (
<TableContainer>
<Table>
<THead>
<Tr>
<Th className="">Name</Th>
<Th className="">Last Used</Th>
<Th className="">Created At</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={4} innerKey="api-keys-v2" />}
{!isLoading &&
data &&
data.length > 0 &&
data.map(({
id,
name,
lastUsed,
createdAt
}) => {
return (
<Tr className="h-10" key={`api-key-v2-${id}`}>
<Td>{name}</Td>
<Td>{lastUsed ? format(new Date(lastUsed), "yyyy-MM-dd") : "-"}</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
<Td className="flex justify-end">
<IconButton
onClick={async () => {
handlePopUpOpen("apiKeyV2", {
apiKeyDataId: id,
name
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
>
<FontAwesomeIcon icon={faPencil} />
</IconButton>
<IconButton
onClick={() => {
handlePopUpOpen("deleteAPIKeyV2", {
apiKeyDataId: id
});
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</Td>
</Tr>
);
})}
{!isLoading && data && data?.length === 0 && (
<Tr>
<Td colSpan={4}>
<EmptyState title="No API key v2 on file" icon={faKey} />
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
);
}
export const APIKeyV2Table = ({ handlePopUpOpen }: Props) => {
const { data, isLoading } = useGetMyAPIKeysV2();
return (
<TableContainer>
<Table>
<THead>
<Tr>
<Th className="">Name</Th>
<Th className="">Last Used</Th>
<Th className="">Created At</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={4} innerKey="api-keys-v2" />}
{!isLoading &&
data &&
data.length > 0 &&
data.map(({ id, name, lastUsed, createdAt }) => {
return (
<Tr className="h-10" key={`api-key-v2-${id}`}>
<Td>{name}</Td>
<Td>{lastUsed ? format(new Date(lastUsed), "yyyy-MM-dd") : "-"}</Td>
<Td>{format(new Date(createdAt), "yyyy-MM-dd")}</Td>
<Td className="flex justify-end">
<IconButton
onClick={async () => {
handlePopUpOpen("apiKeyV2", {
apiKeyDataId: id,
name
});
}}
size="lg"
colorSchema="primary"
variant="plain"
ariaLabel="update"
>
<FontAwesomeIcon icon={faPencil} />
</IconButton>
<IconButton
onClick={() => {
handlePopUpOpen("deleteAPIKeyV2", {
apiKeyDataId: id
});
}}
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
className="ml-4"
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</Td>
</Tr>
);
})}
{!isLoading && data && data?.length === 0 && (
<Tr>
<Td colSpan={4}>
<EmptyState title="No API key v2 on file" icon={faKey} />
</Td>
</Tr>
)}
</TBody>
</Table>
</TableContainer>
);
};
@@ -43,9 +43,9 @@ export const SessionsTable = () => {
{!isLoading &&
data &&
data.length > 0 &&
data.map(({ _id, createdAt, lastUsed, ip, userAgent }) => {
data.map(({ id, createdAt, lastUsed, ip, userAgent }) => {
return (
<Tr className="h-10" key={`session-${_id}`}>
<Tr className="h-10" key={`session-${id}`}>
<Td>{formatDate(createdAt)}</Td>
<Td>{formatDate(lastUsed)}</Td>
<Td>{ip}</Td>
@@ -9,7 +9,7 @@ import { useUser } from "@app/context";
import { useRenameUser } from "@app/hooks/api/users/queries";
const formSchema = yup.object({
name: yup.string().required().label("User Name"),
name: yup.string().required().label("User Name")
});
type FormData = yup.InferType<typeof formSchema>;
@@ -17,11 +17,7 @@ type FormData = yup.InferType<typeof formSchema>;
export const UserNameSection = (): JSX.Element => {
const { user } = useUser();
const { createNotification } = useNotificationContext();
const {
handleSubmit,
control,
reset
} = useForm<FormData>({ resolver: yupResolver(formSchema) });
const { handleSubmit, control, reset } = useForm<FormData>({ resolver: yupResolver(formSchema) });
const { mutateAsync, isLoading } = useRenameUser();
useEffect(() => {
@@ -32,10 +28,10 @@ export const UserNameSection = (): JSX.Element => {
const onFormSubmit = async ({ name }: FormData) => {
try {
if (!user?._id) return;
if (!user?.id) return;
if (name === "") return;
await mutateAsync({ newName: name});
await mutateAsync({ newName: name });
createNotification({
text: "Successfully renamed user",
type: "success"
@@ -50,33 +46,26 @@ export const UserNameSection = (): JSX.Element => {
};
return (
<form
<form
onSubmit={handleSubmit(onFormSubmit)}
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
>
<p className="text-xl font-semibold text-mineshaft-100 mb-4">
Name
</p>
<div className="mb-2 max-w-md">
<Controller
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Input placeholder={`${user?.firstName} ${user?.lastName}`} {...field} />
</FormControl>
)}
control={control}
name="name"
/>
</div>
<Button
isLoading={isLoading}
colorSchema="primary"
variant="outline_bg"
type="submit"
>
Save
</Button>
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Name</p>
<div className="mb-2 max-w-md">
<Controller
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl isError={Boolean(error)} errorText={error?.message}>
<Input placeholder={`${user?.firstName} ${user?.lastName}`} {...field} />
</FormControl>
)}
control={control}
name="name"
/>
</div>
<Button isLoading={isLoading} colorSchema="primary" variant="outline_bg" type="submit">
Save
</Button>
</form>
);
};
@@ -14,10 +14,10 @@ export const AutoCapitalizationSection = () => {
const handleToggleCapitalizationToggle = async (state: boolean) => {
try {
if (!currentWorkspace?._id) return;
if (!currentWorkspace?.id) return;
await mutateAsync({
workspaceID: currentWorkspace._id,
workspaceID: currentWorkspace.id,
state
});
@@ -36,7 +36,7 @@ export const AutoCapitalizationSection = () => {
};
return (
<div className="mb-6 p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-3 text-xl font-semibold">{t("settings.project.auto-capitalization")}</p>
<ProjectPermissionCan I={ProjectPermissionActions.Edit} a={ProjectPermissionSub.Settings}>
{(isAllowed) => (
@@ -2,10 +2,7 @@ import { useRouter } from "next/router";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import { ProjectPermissionCan } from "@app/components/permissions";
import {
Button,
DeleteActionModal,
} from "@app/components/v2";
import { Button, DeleteActionModal } from "@app/components/v2";
import {
ProjectPermissionActions,
ProjectPermissionSub,
@@ -19,30 +16,30 @@ import { usePopUp } from "@app/hooks/usePopUp";
export const DeleteProjectSection = () => {
const router = useRouter();
const { createNotification } = useNotificationContext();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"deleteWorkspace"
] as const);
] as const);
const { currentOrg } = useOrganization();
const { currentWorkspace } = useWorkspace();
const [isDeleting, setIsDeleting] = useToggle();
const deleteWorkspace = useDeleteWorkspace();
const handleDeleteWorkspaceSubmit = async () => {
setIsDeleting.on();
try {
if (!currentWorkspace?._id) return;
if (!currentWorkspace?.id) return;
await deleteWorkspace.mutateAsync({
workspaceID: currentWorkspace?._id
workspaceID: currentWorkspace?.id
});
createNotification({
text: "Successfully deleted project",
type: "success"
});
router.push(`/org/${currentOrg?._id}/overview`);
router.push(`/org/${currentOrg?.id}/overview`);
handlePopUpClose("deleteWorkspace");
} catch (err) {
console.error(err);
@@ -53,36 +50,34 @@ export const DeleteProjectSection = () => {
} finally {
setIsDeleting.off();
}
}
};
return (
<div className="p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600 mb-6">
<p className="text-xl font-semibold text-mineshaft-100 mb-4">
Danger Zone
</p>
<ProjectPermissionCan I={ProjectPermissionActions.Delete} a={ProjectPermissionSub.Workspace}>
{(isAllowed) => (
<Button
isLoading={isDeleting}
isDisabled={!isAllowed || isDeleting}
colorSchema="danger"
variant="outline_bg"
type="submit"
onClick={() => handlePopUpOpen("deleteWorkspace")}
>
{`Delete ${currentWorkspace?.name}`}
</Button>
)}
</ProjectPermissionCan>
<DeleteActionModal
isOpen={popUp.deleteWorkspace.isOpen}
title="Are you sure want to delete this project?"
subTitle={`Permanently delete ${currentWorkspace?.name} and all of its data. This action is not reversible, so please be careful.`}
onChange={(isOpen) => handlePopUpToggle("deleteWorkspace", isOpen)}
deleteKey="confirm"
buttonText="Delete Project"
onDeleteApproved={handleDeleteWorkspaceSubmit}
/>
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<p className="mb-4 text-xl font-semibold text-mineshaft-100">Danger Zone</p>
<ProjectPermissionCan I={ProjectPermissionActions.Delete} a={ProjectPermissionSub.Workspace}>
{(isAllowed) => (
<Button
isLoading={isDeleting}
isDisabled={!isAllowed || isDeleting}
colorSchema="danger"
variant="outline_bg"
type="submit"
onClick={() => handlePopUpOpen("deleteWorkspace")}
>
{`Delete ${currentWorkspace?.name}`}
</Button>
)}
</ProjectPermissionCan>
<DeleteActionModal
isOpen={popUp.deleteWorkspace.isOpen}
title="Are you sure want to delete this project?"
subTitle={`Permanently delete ${currentWorkspace?.name} and all of its data. This action is not reversible, so please be careful.`}
onChange={(isOpen) => handlePopUpToggle("deleteWorkspace", isOpen)}
deleteKey="confirm"
buttonText="Delete Project"
onDeleteApproved={handleDeleteWorkspaceSubmit}
/>
</div>
);
};
@@ -9,9 +9,9 @@ import { useGetUserWsKey, useGetWorkspaceBot, useUpdateBotActiveStatus } from "@
export const E2EESection = () => {
const { currentWorkspace } = useWorkspace();
const { data: bot } = useGetWorkspaceBot(currentWorkspace?._id ?? "");
const { data: bot } = useGetWorkspaceBot(currentWorkspace?.id ?? "");
const { mutateAsync: updateBotActiveStatus } = useUpdateBotActiveStatus();
const { data: wsKey } = useGetUserWsKey(currentWorkspace?._id ?? "");
const { data: wsKey } = useGetUserWsKey(currentWorkspace?.id ?? "");
/**
* Activate bot for project by performing the following steps:
@@ -23,7 +23,7 @@ export const E2EESection = () => {
const toggleBotActivate = async () => {
let botKey;
try {
if (!currentWorkspace?._id) return;
if (!currentWorkspace?.id) return;
if (bot && wsKey) {
// case: there is a bot
@@ -56,17 +56,17 @@ export const E2EESection = () => {
};
await updateBotActiveStatus({
workspaceId: currentWorkspace._id,
workspaceId: currentWorkspace.id,
botKey,
isActive: true,
botId: bot._id
botId: bot.id
});
} else {
// bot is active -> deactivate bot
await updateBotActiveStatus({
isActive: false,
botId: bot._id,
workspaceId: currentWorkspace._id
botId: bot.id,
workspaceId: currentWorkspace.id
});
}
}
@@ -3,21 +3,15 @@ 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 { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { useCreateWsEnvironment } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
popUp: UsePopUpState<["createEnv"]>;
handlePopUpClose: (popUpName: keyof UsePopUpState<["createEnv"]>) => void;
handlePopUpToggle: (popUpName: keyof UsePopUpState<["createEnv"]>, state?: boolean) => void;
popUp: UsePopUpState<["createEnv"]>;
handlePopUpClose: (popUpName: keyof UsePopUpState<["createEnv"]>) => void;
handlePopUpToggle: (popUpName: keyof UsePopUpState<["createEnv"]>, state?: boolean) => void;
};
const schema = yup.object({
@@ -27,106 +21,95 @@ const schema = yup.object({
export type FormData = yup.InferType<typeof schema>;
export const AddEnvironmentModal = ({
popUp,
handlePopUpClose,
handlePopUpToggle
}: Props) => {
export const AddEnvironmentModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
const { createNotification } = useNotificationContext();
const { currentWorkspace } = useWorkspace();
const { mutateAsync, isLoading } = useCreateWsEnvironment();
const {
control,
handleSubmit,
reset
} = useForm<FormData>({
resolver: yupResolver(schema)
});
const { mutateAsync, isLoading } = useCreateWsEnvironment();
const { control, handleSubmit, reset } = useForm<FormData>({
resolver: yupResolver(schema)
});
const onFormSubmit = async ({
const onFormSubmit = async ({ environmentName, environmentSlug }: FormData) => {
try {
if (!currentWorkspace?.id) return;
await mutateAsync({
workspaceID: currentWorkspace.id,
environmentName,
environmentSlug
}: FormData) => {
try {
if (!currentWorkspace?._id) return;
});
await mutateAsync({
workspaceID: currentWorkspace._id,
environmentName,
environmentSlug
});
createNotification({
text: "Successfully created environment",
type: "success"
});
createNotification({
text: "Successfully created environment",
type: "success"
});
handlePopUpClose("createEnv");
} catch (err) {
console.error(err);
createNotification({
text: "Failed to create environment",
type: "error"
});
}
};
handlePopUpClose("createEnv");
} catch (err) {
console.error(err);
createNotification({
text: "Failed to create environment",
type: "error"
});
}
};
return (
<Modal
isOpen={popUp?.createEnv?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("createEnv", isOpen);
reset();
}}
>
<ModalContent title="Create a new environment">
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
defaultValue=""
name="environmentName"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Environment Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="environmentSlug"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Environment Slug"
helperText="Slugs are shorthands used in cli to access environment"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isLoading}
isDisabled={isLoading}
>
Create
</Button>
return (
<Modal
isOpen={popUp?.createEnv?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("createEnv", isOpen);
reset();
}}
>
<ModalContent title="Create a new environment">
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
defaultValue=""
name="environmentName"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Environment Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="environmentSlug"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Environment Slug"
helperText="Slugs are shorthands used in cli to access environment"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isLoading}
isDisabled={isLoading}
>
Create
</Button>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
}
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
};
@@ -40,10 +40,10 @@ export const EnvironmentSection = () => {
const onEnvDeleteSubmit = async (environmentSlug: string) => {
try {
if (!currentWorkspace?._id) return;
if (!currentWorkspace?.id) return;
await deleteWsEnvironment.mutateAsync({
workspaceID: currentWorkspace._id,
workspaceID: currentWorkspace.id,
environmentSlug
});
@@ -63,8 +63,8 @@ export const EnvironmentSection = () => {
};
return (
<div className="mb-6 p-4 bg-mineshaft-900 rounded-lg border border-mineshaft-600">
<div className="flex justify-between mb-8">
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-8 flex justify-between">
<p className="text-xl font-semibold text-mineshaft-100">Environments</p>
<div>
<ProjectPermissionCan
@@ -90,7 +90,7 @@ export const EnvironmentSection = () => {
</ProjectPermissionCan>
</div>
</div>
<p className="text-gray-400 mb-8">
<p className="mb-8 text-gray-400">
Choose which environments will show up in your dashboard like development, staging,
production
</p>
@@ -39,7 +39,7 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
const handleReorderEnv = async (shouldMoveUp: boolean, name: string, slug: string) => {
try {
if (!currentWorkspace?._id) return;
if (!currentWorkspace?.id) return;
const indexOfEnv = currentWorkspace.environments.findIndex(
(env) => env.name === name && env.slug === slug
@@ -56,7 +56,7 @@ export const EnvironmentTable = ({ handlePopUpOpen }: Props) => {
const indexToSwap = shouldMoveUp ? indexOfEnv - 1 : indexOfEnv + 1;
await reorderWsEnvironment.mutateAsync({
workspaceID: currentWorkspace._id,
workspaceID: currentWorkspace.id,
environmentSlug: slug,
environmentName: name,
otherEnvironmentSlug: currentWorkspace.environments[indexToSwap].slug,
@@ -3,21 +3,15 @@ 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 { Button, FormControl, Input, Modal, ModalContent } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { useUpdateWsEnvironment } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
type Props = {
popUp: UsePopUpState<["updateEnv"]>;
handlePopUpClose: (popUpName: keyof UsePopUpState<["updateEnv"]>) => void;
handlePopUpToggle: (popUpName: keyof UsePopUpState<["updateEnv"]>, state?: boolean) => void;
popUp: UsePopUpState<["updateEnv"]>;
handlePopUpClose: (popUpName: keyof UsePopUpState<["updateEnv"]>) => void;
handlePopUpToggle: (popUpName: keyof UsePopUpState<["updateEnv"]>, state?: boolean) => void;
};
const schema = yup.object({
@@ -27,109 +21,98 @@ const schema = yup.object({
export type FormData = yup.InferType<typeof schema>;
export const UpdateEnvironmentModal = ({
popUp,
handlePopUpClose,
handlePopUpToggle
}: Props) => {
export const UpdateEnvironmentModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
const { createNotification } = useNotificationContext();
const { currentWorkspace } = useWorkspace();
const { mutateAsync, isLoading } = useUpdateWsEnvironment();
const {
control,
handleSubmit,
reset
} = useForm<FormData>({
resolver: yupResolver(schema)
});
const { mutateAsync, isLoading } = useUpdateWsEnvironment();
const { control, handleSubmit, reset } = useForm<FormData>({
resolver: yupResolver(schema)
});
const oldEnvironmentSlug = (popUp?.updateEnv?.data as { slug: string })?.slug;
const onFormSubmit = async ({
const oldEnvironmentSlug = (popUp?.updateEnv?.data as { slug: string })?.slug;
const onFormSubmit = async ({ environmentName, environmentSlug }: FormData) => {
try {
if (!currentWorkspace?.id) return;
await mutateAsync({
workspaceID: currentWorkspace.id,
environmentName,
environmentSlug
}: FormData) => {
try {
if (!currentWorkspace?._id) return;
await mutateAsync({
workspaceID: currentWorkspace._id,
environmentName,
environmentSlug,
oldEnvironmentSlug
});
environmentSlug,
oldEnvironmentSlug
});
createNotification({
text: "Successfully updated environment",
type: "success"
});
handlePopUpClose("updateEnv");
} catch (err) {
console.error(err);
createNotification({
text: "Failed to update environment",
type: "error"
});
}
};
createNotification({
text: "Successfully updated environment",
type: "success"
});
return (
<Modal
isOpen={popUp?.updateEnv?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("updateEnv", isOpen);
reset();
}}
>
<ModalContent title="Update environment">
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
defaultValue=""
name="environmentName"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Environment Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="environmentSlug"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Environment Slug"
helperText="Slugs are shorthands used in cli to access environment"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isLoading}
isDisabled={isLoading}
>
Update
</Button>
handlePopUpClose("updateEnv");
} catch (err) {
console.error(err);
createNotification({
text: "Failed to update environment",
type: "error"
});
}
};
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
}
return (
<Modal
isOpen={popUp?.updateEnv?.isOpen}
onOpenChange={(isOpen) => {
handlePopUpToggle("updateEnv", isOpen);
reset();
}}
>
<ModalContent title="Update environment">
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
defaultValue=""
name="environmentName"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Environment Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
<Controller
control={control}
defaultValue=""
name="environmentSlug"
render={({ field, fieldState: { error } }) => (
<FormControl
label="Environment Slug"
helperText="Slugs are shorthands used in cli to access environment"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} />
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
size="sm"
type="submit"
isLoading={isLoading}
isDisabled={isLoading}
>
Update
</Button>
<Button colorSchema="secondary" variant="plain">
Cancel
</Button>
</div>
</form>
</ModalContent>
</Modal>
);
};
@@ -32,10 +32,10 @@ export const ProjectNameChangeSection = () => {
const onFormSubmit = async ({ name }: FormData) => {
try {
if (!currentWorkspace?._id) return;
if (!currentWorkspace?.id) return;
await mutateAsync({
workspaceID: currentWorkspace._id,
workspaceID: currentWorkspace.id,
newWorkspaceName: name
});
@@ -55,9 +55,9 @@ export const ProjectNameChangeSection = () => {
return (
<form
onSubmit={handleSubmit(onFormSubmit)}
className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600"
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
>
<h2 className="text-xl font-semibold flex-1 text-mineshaft-100 mb-8">Project Name</h2>
<h2 className="mb-8 flex-1 text-xl font-semibold text-mineshaft-100">Project Name</h2>
<div className="max-w-md">
<Controller
defaultValue=""
@@ -3,120 +3,108 @@ import { yupResolver } from "@hookform/resolvers/yup";
import * as yup from "yup";
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
import {
Button,
FormControl,
Input,
Modal,
ModalClose,
ModalContent
} from "@app/components/v2";
import { Button, FormControl, Input, Modal, ModalClose, ModalContent } from "@app/components/v2";
import { useWorkspace } from "@app/context";
import { useCreateWsTag } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
const schema = yup.object({
name: yup.string().required().label("Tag Name")
name: yup.string().required().label("Tag Name")
});
export type FormData = yup.InferType<typeof schema>;
type Props = {
popUp: UsePopUpState<["CreateSecretTag", "deleteTagConfirmation"]>;
handlePopUpClose: (popUpName: keyof UsePopUpState<["CreateSecretTag", "deleteTagConfirmation"]>) => void;
handlePopUpToggle: (popUpName: keyof UsePopUpState<["CreateSecretTag", "deleteTagConfirmation"]>, state?: boolean) => void;
popUp: UsePopUpState<["CreateSecretTag", "deleteTagConfirmation"]>;
handlePopUpClose: (
popUpName: keyof UsePopUpState<["CreateSecretTag", "deleteTagConfirmation"]>
) => void;
handlePopUpToggle: (
popUpName: keyof UsePopUpState<["CreateSecretTag", "deleteTagConfirmation"]>,
state?: boolean
) => void;
};
export const AddSecretTagModal = ({
popUp,
handlePopUpClose,
handlePopUpToggle
}: Props) => {
const { createNotification } = useNotificationContext();
const { currentWorkspace }= useWorkspace();
const createWsTag = useCreateWsTag();
const {
control,
reset,
handleSubmit,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: yupResolver(schema)
});
const onFormSubmit = async ({
name
}: FormData) => {
try {
if (!currentWorkspace?._id) return;
export const AddSecretTagModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
const { createNotification } = useNotificationContext();
const { currentWorkspace } = useWorkspace();
const createWsTag = useCreateWsTag();
const {
control,
reset,
handleSubmit,
formState: { isSubmitting }
} = useForm<FormData>({
resolver: yupResolver(schema)
});
await createWsTag.mutateAsync({
workspaceID: currentWorkspace?._id,
tagName: name,
tagSlug: name.replace(/\s+/g, " ").replace(" ", "_"),
tagColor: ""
});
const onFormSubmit = async ({ name }: FormData) => {
try {
if (!currentWorkspace?.id) return;
handlePopUpClose("CreateSecretTag");
await createWsTag.mutateAsync({
workspaceID: currentWorkspace?.id,
tagName: name,
tagSlug: name.replace(/\s+/g, " ").replace(" ", "_"),
tagColor: ""
});
createNotification({
text: "Successfully created a tag",
type: "success"
});
reset()
} catch (err) {
console.error(err);
createNotification({
text: "Failed to create a tag",
type: "error"
});
}
handlePopUpClose("CreateSecretTag");
createNotification({
text: "Successfully created a tag",
type: "success"
});
reset();
} catch (err) {
console.error(err);
createNotification({
text: "Failed to create a tag",
type: "error"
});
}
};
return (
<Modal
isOpen={popUp?.CreateSecretTag?.isOpen}
onOpenChange={(open) => {
handlePopUpToggle("CreateSecretTag", open);
reset();
}}
>
<ModalContent
title={`Add a tag for ${currentWorkspace?.name ?? ""}`}
subTitle="Specify your tag name, and the slug will be created automatically."
return (
<Modal
isOpen={popUp?.CreateSecretTag?.isOpen}
onOpenChange={(open) => {
handlePopUpToggle("CreateSecretTag", open);
reset();
}}
>
<ModalContent
title={`Add a tag for ${currentWorkspace?.name ?? ""}`}
subTitle="Specify your tag name, and the slug will be created automatically."
>
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
name="name"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl label="Tag Name" isError={Boolean(error)} errorText={error?.message}>
<Input {...field} placeholder="Type your tag name" />
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
type="submit"
isDisabled={isSubmitting}
isLoading={isSubmitting}
>
<form onSubmit={handleSubmit(onFormSubmit)}>
<Controller
control={control}
name="name"
defaultValue=""
render={({ field, fieldState: { error } }) => (
<FormControl
label="Tag Name"
isError={Boolean(error)}
errorText={error?.message}
>
<Input {...field} placeholder="Type your tag name" />
</FormControl>
)}
/>
<div className="mt-8 flex items-center">
<Button
className="mr-4"
type="submit"
isDisabled={isSubmitting}
isLoading={isSubmitting}
>
Create
</Button>
<ModalClose asChild>
<Button variant="plain" colorSchema="secondary">
Cancel
</Button>
</ModalClose>
</div>
</form>
</ModalContent>
</Modal>
);
}
Create
</Button>
<ModalClose asChild>
<Button variant="plain" colorSchema="secondary">
Cancel
</Button>
</ModalClose>
</div>
</form>
</ModalContent>
</Modal>
);
};
@@ -33,7 +33,7 @@ type Props = {
export const SecretTagsTable = ({ handlePopUpOpen }: Props) => {
const { currentWorkspace } = useWorkspace();
const { data, isLoading } = useGetWsTags(currentWorkspace?._id ?? "");
const { data, isLoading } = useGetWsTags(currentWorkspace?.id ?? "");
return (
<TableContainer className="mt-4">
@@ -49,7 +49,7 @@ export const SecretTagsTable = ({ handlePopUpOpen }: Props) => {
{isLoading && <TableSkeleton columns={3} innerKey="secret-tags" />}
{!isLoading &&
data &&
data.map(({ _id, name, slug }) => (
data.map(({ id, name, slug }) => (
<Tr key={name}>
<Td>{name}</Td>
<Td>{slug}</Td>
@@ -63,7 +63,7 @@ export const SecretTagsTable = ({ handlePopUpOpen }: Props) => {
onClick={() =>
handlePopUpOpen("deleteTagConfirmation", {
name,
id: _id
id
})
}
colorSchema="danger"
@@ -36,7 +36,7 @@ export const WebhooksTab = withProjectPermission(
const { t } = useTranslation();
const { createNotification } = useNotificationContext();
const { currentWorkspace } = useWorkspace();
const workspaceId = currentWorkspace?._id || "";
const workspaceId = currentWorkspace?.id || "";
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
"addWebhook",
"deleteWebhook"
@@ -182,7 +182,7 @@ export const WebhooksTab = withProjectPermission(
{!isWebhooksLoading &&
webhooks?.map(
({
_id: id,
id,
url,
environment,
secretPath,