mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-17 04:33:01 +00:00
Merge pull request #1591 from Infisical/daniel/prettier-fix
Chore: Prettier formatting
This commit is contained in:
+5
-1
@@ -207,7 +207,11 @@ export const IntegrationsSection = ({
|
||||
(popUp?.deleteConfirmation.data as TIntegration)?.integration || " "
|
||||
} integration for ${(popUp?.deleteConfirmation.data as TIntegration)?.app || " "}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteConfirmation", isOpen)}
|
||||
deleteKey={(popUp?.deleteConfirmation?.data as TIntegration)?.app || (popUp?.deleteConfirmation?.data as TIntegration)?.owner || ""}
|
||||
deleteKey={
|
||||
(popUp?.deleteConfirmation?.data as TIntegration)?.app ||
|
||||
(popUp?.deleteConfirmation?.data as TIntegration)?.owner ||
|
||||
""
|
||||
}
|
||||
onDeleteApproved={async () =>
|
||||
onIntegrationDelete(popUp?.deleteConfirmation.data as TIntegration, () =>
|
||||
handlePopUpClose("deleteConfirmation")
|
||||
|
||||
@@ -1,62 +1,46 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import jwt_decode from "jwt-decode";
|
||||
|
||||
import {
|
||||
MFAStep,
|
||||
PasswordStep
|
||||
} from "./components";
|
||||
import { MFAStep, PasswordStep } from "./components";
|
||||
|
||||
type Props = {
|
||||
providerAuthToken: string;
|
||||
}
|
||||
type Props = {
|
||||
providerAuthToken: string;
|
||||
};
|
||||
|
||||
export const LoginSSO = ({ providerAuthToken }: Props) => {
|
||||
const [step, setStep] = useState(0);
|
||||
const [password, setPassword] = useState("");
|
||||
const [step, setStep] = useState(0);
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
const {
|
||||
username,
|
||||
isUserCompleted
|
||||
} = jwt_decode(providerAuthToken) as any;
|
||||
|
||||
useEffect(() => {
|
||||
if (isUserCompleted) {
|
||||
setStep(1);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderView = () => {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return (
|
||||
<div />
|
||||
);
|
||||
case 1:
|
||||
return (
|
||||
<PasswordStep
|
||||
providerAuthToken={providerAuthToken}
|
||||
email={username}
|
||||
password={password}
|
||||
setPassword={setPassword}
|
||||
setStep={setStep}
|
||||
/>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<MFAStep
|
||||
providerAuthToken={providerAuthToken}
|
||||
email={username}
|
||||
password={password}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
return <div />;
|
||||
}
|
||||
const { username, isUserCompleted } = jwt_decode(providerAuthToken) as any;
|
||||
|
||||
useEffect(() => {
|
||||
if (isUserCompleted) {
|
||||
setStep(1);
|
||||
}
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{renderView()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const renderView = () => {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return <div />;
|
||||
case 1:
|
||||
return (
|
||||
<PasswordStep
|
||||
providerAuthToken={providerAuthToken}
|
||||
email={username}
|
||||
password={password}
|
||||
setPassword={setPassword}
|
||||
setStep={setStep}
|
||||
/>
|
||||
);
|
||||
case 2:
|
||||
return (
|
||||
<MFAStep providerAuthToken={providerAuthToken} email={username} password={password} />
|
||||
);
|
||||
default:
|
||||
return <div />;
|
||||
}
|
||||
};
|
||||
|
||||
return <div>{renderView()}</div>;
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { InitialStep } from "./InitialStep";
|
||||
export { InitialStep } from "./InitialStep";
|
||||
|
||||
@@ -6,132 +6,130 @@ import { Button, Input } from "@app/components/v2";
|
||||
import { loginLDAPRedirect } from "@app/hooks/api/auth/queries";
|
||||
|
||||
type Props = {
|
||||
setStep: (step: number) => void;
|
||||
}
|
||||
setStep: (step: number) => void;
|
||||
};
|
||||
|
||||
export const LDAPStep = ({
|
||||
setStep
|
||||
}: Props) => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const [organizationSlug, setOrganizationSlug] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
const { t } = useTranslation();
|
||||
export const LDAPStep = ({ setStep }: Props) => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const [organizationSlug, setOrganizationSlug] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
// const queryParams = new URLSearchParams(window.location.search);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleSubmission = async (e:React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
try {
|
||||
const { nextUrl } = await loginLDAPRedirect({
|
||||
organizationSlug,
|
||||
username,
|
||||
password
|
||||
});
|
||||
|
||||
if (!nextUrl) {
|
||||
createNotification({
|
||||
text: "Login unsuccessful. Double-check your credentials and try again.",
|
||||
type: "error"
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
createNotification({
|
||||
text: "Successfully logged in",
|
||||
type: "success"
|
||||
});
|
||||
// const queryParams = new URLSearchParams(window.location.search);
|
||||
|
||||
// redirects either to /login/sso or /signup/sso
|
||||
window.open(nextUrl);
|
||||
window.close();
|
||||
} catch (err) {
|
||||
createNotification({
|
||||
text: "Login unsuccessful. Double-check your credentials and try again.",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
// TODO: add callback port support
|
||||
|
||||
// const callbackPort = queryParams.get("callback_port");
|
||||
// window.open(`/api/v1/ldap/redirect/saml2/${ssoIdentifier}${callbackPort ? `?callback_port=${callbackPort}` : ""}`);
|
||||
// window.close();
|
||||
const handleSubmission = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
try {
|
||||
const { nextUrl } = await loginLDAPRedirect({
|
||||
organizationSlug,
|
||||
username,
|
||||
password
|
||||
});
|
||||
|
||||
if (!nextUrl) {
|
||||
createNotification({
|
||||
text: "Login unsuccessful. Double-check your credentials and try again.",
|
||||
type: "error"
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
createNotification({
|
||||
text: "Successfully logged in",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
// redirects either to /login/sso or /signup/sso
|
||||
window.open(nextUrl);
|
||||
window.close();
|
||||
} catch (err) {
|
||||
createNotification({
|
||||
text: "Login unsuccessful. Double-check your credentials and try again.",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md md:px-6">
|
||||
<p className="mx-auto mb-6 flex w-max justify-center text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8">
|
||||
What's your LDAP Login?
|
||||
</p>
|
||||
<form onSubmit={handleSubmission}>
|
||||
<div className="relative flex items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] md:min-w-[22rem] mx-auto w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<div className="flex items-center justify-center w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<Input
|
||||
value={organizationSlug}
|
||||
onChange={(e) => setOrganizationSlug(e.target.value)}
|
||||
type="text"
|
||||
placeholder="Enter your organization slug..."
|
||||
isRequired
|
||||
autoComplete="email"
|
||||
id="email"
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 relative flex items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] md:min-w-[22rem] mx-auto w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<div className="flex items-center justify-center w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<Input
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
type="text"
|
||||
placeholder="Enter your LDAP username..."
|
||||
isRequired
|
||||
autoComplete="email"
|
||||
id="email"
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 relative flex items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] md:min-w-[22rem] mx-auto w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<div className="flex items-center justify-center w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<Input
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
type="password"
|
||||
placeholder="Enter your LDAP password..."
|
||||
isRequired
|
||||
autoComplete="current-password"
|
||||
id="current-password"
|
||||
className="select:-webkit-autofill:focus h-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='lg:w-1/6 w-1/4 w-full mx-auto flex items-center justify-center min-w-[20rem] md:min-w-[22rem] text-center rounded-md mt-4'>
|
||||
<Button
|
||||
type="submit"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
isFullWidth
|
||||
className="h-14"
|
||||
>
|
||||
{t("login.login")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="flex flex-row items-center justify-center mt-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
setStep(0);
|
||||
}}
|
||||
type="button"
|
||||
className="text-bunker-300 text-sm hover:underline mt-2 hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer"
|
||||
>
|
||||
{t("login.other-option")}
|
||||
</button>
|
||||
</div>
|
||||
// TODO: add callback port support
|
||||
|
||||
// const callbackPort = queryParams.get("callback_port");
|
||||
// window.open(`/api/v1/ldap/redirect/saml2/${ssoIdentifier}${callbackPort ? `?callback_port=${callbackPort}` : ""}`);
|
||||
// window.close();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md md:px-6">
|
||||
<p className="mx-auto mb-6 mb-8 flex w-max justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||
What's your LDAP Login?
|
||||
</p>
|
||||
<form onSubmit={handleSubmission}>
|
||||
<div className="relative mx-auto flex max-h-24 w-1/4 w-full min-w-[20rem] items-center justify-center rounded-lg md:max-h-28 md:min-w-[22rem] lg:w-1/6">
|
||||
<div className="flex max-h-24 w-full items-center justify-center rounded-lg md:max-h-28">
|
||||
<Input
|
||||
value={organizationSlug}
|
||||
onChange={(e) => setOrganizationSlug(e.target.value)}
|
||||
type="text"
|
||||
placeholder="Enter your organization slug..."
|
||||
isRequired
|
||||
autoComplete="email"
|
||||
id="email"
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
<div className="relative mx-auto mt-2 flex max-h-24 w-1/4 w-full min-w-[20rem] items-center justify-center rounded-lg md:max-h-28 md:min-w-[22rem] lg:w-1/6">
|
||||
<div className="flex max-h-24 w-full items-center justify-center rounded-lg md:max-h-28">
|
||||
<Input
|
||||
value={username}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
type="text"
|
||||
placeholder="Enter your LDAP username..."
|
||||
isRequired
|
||||
autoComplete="email"
|
||||
id="email"
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="relative mx-auto mt-2 flex max-h-24 w-1/4 w-full min-w-[20rem] items-center justify-center rounded-lg md:max-h-28 md:min-w-[22rem] lg:w-1/6">
|
||||
<div className="flex max-h-24 w-full items-center justify-center rounded-lg md:max-h-28">
|
||||
<Input
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
type="password"
|
||||
placeholder="Enter your LDAP password..."
|
||||
isRequired
|
||||
autoComplete="current-password"
|
||||
id="current-password"
|
||||
className="select:-webkit-autofill:focus h-10"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto mt-4 flex w-1/4 w-full min-w-[20rem] items-center justify-center rounded-md text-center md:min-w-[22rem] lg:w-1/6">
|
||||
<Button
|
||||
type="submit"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
isFullWidth
|
||||
className="h-14"
|
||||
>
|
||||
{t("login.login")}
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="mt-4 flex flex-row items-center justify-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
setStep(0);
|
||||
}}
|
||||
type="button"
|
||||
className="mt-2 cursor-pointer text-sm text-bunker-300 duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4"
|
||||
>
|
||||
{t("login.other-option")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { LDAPStep } from "./LDAPStep";
|
||||
export { LDAPStep } from "./LDAPStep";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { MFAStep } from "./MFAStep";
|
||||
export { MFAStep } from "./MFAStep";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { PasswordStep } from "./PasswordStep";
|
||||
export { PasswordStep } from "./PasswordStep";
|
||||
|
||||
@@ -4,67 +4,69 @@ import { useTranslation } from "react-i18next";
|
||||
import { Button, Input } from "@app/components/v2";
|
||||
|
||||
type Props = {
|
||||
setStep: (step: number) => void;
|
||||
}
|
||||
setStep: (step: number) => void;
|
||||
};
|
||||
|
||||
export const SAMLSSOStep = ({
|
||||
setStep
|
||||
}: Props) => {
|
||||
const [ssoIdentifier, setSSOIdentifier] = useState("");
|
||||
const { t } = useTranslation();
|
||||
export const SAMLSSOStep = ({ setStep }: Props) => {
|
||||
const [ssoIdentifier, setSSOIdentifier] = useState("");
|
||||
const { t } = useTranslation();
|
||||
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
const queryParams = new URLSearchParams(window.location.search);
|
||||
|
||||
const handleSubmission = (e:React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
const callbackPort = queryParams.get("callback_port");
|
||||
window.open(`/api/v1/sso/redirect/saml2/organizations/${ssoIdentifier}${callbackPort ? `?callback_port=${callbackPort}` : ""}`);
|
||||
window.close();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md md:px-6">
|
||||
<p className="mx-auto mb-6 flex w-max justify-center text-xl font-medium text-transparent bg-clip-text bg-gradient-to-b from-white to-bunker-200 text-center mb-8">
|
||||
What's your organization slug?
|
||||
</p>
|
||||
<form onSubmit={handleSubmission}>
|
||||
<div className="relative flex items-center justify-center lg:w-1/6 w-1/4 min-w-[20rem] md:min-w-[22rem] mx-auto w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<div className="flex items-center justify-center w-full rounded-lg max-h-24 md:max-h-28">
|
||||
<Input
|
||||
value={ssoIdentifier}
|
||||
onChange={(e) => setSSOIdentifier(e.target.value)}
|
||||
type="text"
|
||||
placeholder="acme-123"
|
||||
isRequired
|
||||
autoComplete="email"
|
||||
id="email"
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='lg:w-1/6 w-1/4 w-full mx-auto flex items-center justify-center min-w-[20rem] md:min-w-[22rem] text-center rounded-md mt-4'>
|
||||
<Button
|
||||
type="submit"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
isFullWidth
|
||||
className="h-14"
|
||||
>
|
||||
Continue with SAML
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="flex flex-row items-center justify-center mt-4">
|
||||
<button
|
||||
onClick={() => {
|
||||
setStep(0);
|
||||
}}
|
||||
type="button"
|
||||
className="text-bunker-300 text-sm hover:underline mt-2 hover:underline-offset-4 hover:decoration-primary-700 hover:text-bunker-200 duration-200 cursor-pointer"
|
||||
>
|
||||
{t("login.other-option")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
const handleSubmission = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
const callbackPort = queryParams.get("callback_port");
|
||||
window.open(
|
||||
`/api/v1/sso/redirect/saml2/organizations/${ssoIdentifier}${
|
||||
callbackPort ? `?callback_port=${callbackPort}` : ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
window.close();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mx-auto w-full max-w-md md:px-6">
|
||||
<p className="mx-auto mb-6 mb-8 flex w-max justify-center bg-gradient-to-b from-white to-bunker-200 bg-clip-text text-center text-xl font-medium text-transparent">
|
||||
What's your organization slug?
|
||||
</p>
|
||||
<form onSubmit={handleSubmission}>
|
||||
<div className="relative mx-auto flex max-h-24 w-1/4 w-full min-w-[20rem] items-center justify-center rounded-lg md:max-h-28 md:min-w-[22rem] lg:w-1/6">
|
||||
<div className="flex max-h-24 w-full items-center justify-center rounded-lg md:max-h-28">
|
||||
<Input
|
||||
value={ssoIdentifier}
|
||||
onChange={(e) => setSSOIdentifier(e.target.value)}
|
||||
type="text"
|
||||
placeholder="acme-123"
|
||||
isRequired
|
||||
autoComplete="email"
|
||||
id="email"
|
||||
className="h-12"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mx-auto mt-4 flex w-1/4 w-full min-w-[20rem] items-center justify-center rounded-md text-center md:min-w-[22rem] lg:w-1/6">
|
||||
<Button
|
||||
type="submit"
|
||||
colorSchema="primary"
|
||||
variant="outline_bg"
|
||||
isFullWidth
|
||||
className="h-14"
|
||||
>
|
||||
Continue with SAML
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
<div className="mt-4 flex flex-row items-center justify-center">
|
||||
<button
|
||||
onClick={() => {
|
||||
setStep(0);
|
||||
}}
|
||||
type="button"
|
||||
className="mt-2 cursor-pointer text-sm text-bunker-300 duration-200 hover:text-bunker-200 hover:underline hover:decoration-primary-700 hover:underline-offset-4"
|
||||
>
|
||||
{t("login.other-option")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { SAMLSSOStep } from "./SAMLSSOStep";
|
||||
export { SAMLSSOStep } from "./SAMLSSOStep";
|
||||
|
||||
@@ -4,4 +4,4 @@ export { MFAStep } from "./MFAStep";
|
||||
export { SAMLSSOStep } from "./SAMLSSOStep";
|
||||
|
||||
// SSO-specific step
|
||||
export { PasswordStep } from "./PasswordStep";
|
||||
export { PasswordStep } from "./PasswordStep";
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import {
|
||||
IdentitySection
|
||||
} from "./components";
|
||||
import { IdentitySection } from "./components";
|
||||
|
||||
export const OrgIdentityTab = () => {
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-service-token"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<IdentitySection />
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-service-token"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<IdentitySection />
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
+84
-87
@@ -1,14 +1,14 @@
|
||||
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 {
|
||||
FormControl,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Select,
|
||||
SelectItem,
|
||||
UpgradePlanModal
|
||||
FormControl,
|
||||
Modal,
|
||||
ModalContent,
|
||||
Select,
|
||||
SelectItem,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import { IdentityAuthMethod } from "@app/hooks/api/identities";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
@@ -16,93 +16,90 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
import { IdentityUniversalAuthForm } from "./IdentityUniversalAuthForm";
|
||||
|
||||
type Props = {
|
||||
popUp: UsePopUpState<["identityAuthMethod", "upgradePlan"]>;
|
||||
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan"]>, state?: boolean) => void;
|
||||
}
|
||||
popUp: UsePopUpState<["identityAuthMethod", "upgradePlan"]>;
|
||||
handlePopUpOpen: (popUpName: keyof UsePopUpState<["upgradePlan"]>) => void;
|
||||
handlePopUpToggle: (
|
||||
popUpName: keyof UsePopUpState<["identityAuthMethod", "upgradePlan"]>,
|
||||
state?: boolean
|
||||
) => void;
|
||||
};
|
||||
|
||||
const identityAuthMethods = [
|
||||
{ label: "Universal Auth", value: IdentityAuthMethod.UNIVERSAL_AUTH }
|
||||
];
|
||||
const identityAuthMethods = [{ label: "Universal Auth", value: IdentityAuthMethod.UNIVERSAL_AUTH }];
|
||||
|
||||
const schema = yup.object({
|
||||
authMethod: yup.string().required("Auth method is required"), // TODO: better enforcement here
|
||||
}).required();
|
||||
const schema = yup
|
||||
.object({
|
||||
authMethod: yup.string().required("Auth method is required") // TODO: better enforcement here
|
||||
})
|
||||
.required();
|
||||
|
||||
export type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
export const IdentityAuthMethodModal = ({
|
||||
popUp,
|
||||
handlePopUpOpen,
|
||||
handlePopUpToggle
|
||||
}: Props) => {
|
||||
export const IdentityAuthMethodModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Props) => {
|
||||
const {
|
||||
control
|
||||
// watch,
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
|
||||
const {
|
||||
control,
|
||||
// watch,
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
const identityAuthMethodData = popUp?.identityAuthMethod?.data as {
|
||||
identityId: string;
|
||||
name: string;
|
||||
authMethod?: IdentityAuthMethod;
|
||||
};
|
||||
|
||||
const identityAuthMethodData = popUp?.identityAuthMethod?.data as {
|
||||
identityId: string;
|
||||
name: string;
|
||||
authMethod?: IdentityAuthMethod;
|
||||
}
|
||||
|
||||
// const authMethod = watch("authMethod");
|
||||
|
||||
const renderIdentityAuthForm = () => {
|
||||
return (
|
||||
<IdentityUniversalAuthForm
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
identityAuthMethodData={identityAuthMethodData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
// const authMethod = watch("authMethod");
|
||||
|
||||
const renderIdentityAuthForm = () => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.identityAuthMethod?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("identityAuthMethod", isOpen);
|
||||
}}
|
||||
>
|
||||
<ModalContent title={`${identityAuthMethodData?.authMethod ? "Update" : "Configure"} Identity Auth Method for ${identityAuthMethodData?.name ?? ""}`}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="authMethod"
|
||||
defaultValue={IdentityAuthMethod.UNIVERSAL_AUTH}
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Auth Method"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{identityAuthMethods.map(({ label, value }) => (
|
||||
<SelectItem value={String(value || "")} key={label}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{renderIdentityAuthForm()}
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp?.upgradePlan?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can use IP allowlisting if you switch to Infisical's Pro plan."
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
<IdentityUniversalAuthForm
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
identityAuthMethodData={identityAuthMethodData}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.identityAuthMethod?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("identityAuthMethod", isOpen);
|
||||
}}
|
||||
>
|
||||
<ModalContent
|
||||
title={`${
|
||||
identityAuthMethodData?.authMethod ? "Update" : "Configure"
|
||||
} Identity Auth Method for ${identityAuthMethodData?.name ?? ""}`}
|
||||
>
|
||||
<Controller
|
||||
control={control}
|
||||
name="authMethod"
|
||||
defaultValue={IdentityAuthMethod.UNIVERSAL_AUTH}
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl label="Auth Method" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{identityAuthMethods.map(({ label, value }) => (
|
||||
<SelectItem value={String(value || "")} key={label}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
{renderIdentityAuthForm()}
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp?.upgradePlan?.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can use IP allowlisting if you switch to Infisical's Pro plan."
|
||||
/>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export { IdentitySection } from "./IdentitySection";
|
||||
export { IdentitySection } from "./IdentitySection";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { IdentitySection } from "./IdentitySection";
|
||||
export { IdentitySection } from "./IdentitySection";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { OrgIdentityTab } from "./OrgIdentityTab";
|
||||
export { OrgIdentityTab } from "./OrgIdentityTab";
|
||||
|
||||
@@ -3,15 +3,15 @@ import { motion } from "framer-motion";
|
||||
import { OrgMembersSection } from "./components";
|
||||
|
||||
export const OrgMembersTab = () => {
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-service-token"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<OrgMembersSection />
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-service-token"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<OrgMembersSection />
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ export const OrgMembersSection = () => {
|
||||
const { subscription } = useSubscription();
|
||||
const { currentOrg } = useOrganization();
|
||||
const orgId = currentOrg?.id ?? "";
|
||||
|
||||
|
||||
const [completeInviteLink, setCompleteInviteLink] = useState<string>("");
|
||||
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
|
||||
+3
-2
@@ -208,7 +208,8 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
{(status === "invited" || status === "verified") && email &&
|
||||
{(status === "invited" || status === "verified") &&
|
||||
email &&
|
||||
serverDetails?.emailConfigured && (
|
||||
<Button
|
||||
isDisabled={!isAllowed}
|
||||
@@ -240,7 +241,7 @@ export const OrgMembersTable = ({ handlePopUpOpen, setCompleteInviteLink }: Prop
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
handlePopUpOpen("removeMember", { orgMembershipId, username });
|
||||
}}
|
||||
size="lg"
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export { OrgMembersSection } from "./OrgMembersSection";
|
||||
export { OrgMembersSection } from "./OrgMembersSection";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { OrgMembersSection } from "./OrgMembersSection";
|
||||
export { OrgMembersSection } from "./OrgMembersSection";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { OrgMembersTab } from "./OrgMembersTab";
|
||||
export { OrgMembersTab } from "./OrgMembersTab";
|
||||
|
||||
+5
-5
@@ -71,7 +71,7 @@ const getPermissionList = (option: Props["formName"]) => {
|
||||
case "incident-contact":
|
||||
return INCIDENT_CONTACTS_PERMISSIONS;
|
||||
case "member":
|
||||
return MEMBERS_PERMISSIONS
|
||||
return MEMBERS_PERMISSIONS;
|
||||
default:
|
||||
return PERMISSIONS;
|
||||
}
|
||||
@@ -149,7 +149,7 @@ export const SimpleLevelPermissionOption = ({
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
||||
"rounded-md bg-mineshaft-800 px-10 py-6",
|
||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
||||
)}
|
||||
>
|
||||
@@ -157,8 +157,8 @@ export const SimpleLevelPermissionOption = ({
|
||||
<div>
|
||||
<FontAwesomeIcon icon={icon} className="text-4xl" />
|
||||
</div>
|
||||
<div className="flex-grow flex flex-col">
|
||||
<div className="font-medium mb-1 text-lg">{title}</div>
|
||||
<div className="flex flex-grow flex-col">
|
||||
<div className="mb-1 text-lg font-medium">{title}</div>
|
||||
<div className="text-xs font-light">{subtitle}</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -178,7 +178,7 @@ export const SimpleLevelPermissionOption = ({
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
||||
className="grid auto-cols-min grid-flow-col gap-8 overflow-hidden"
|
||||
>
|
||||
{isCustom &&
|
||||
getPermissionList(formName).map(({ action, label }) => (
|
||||
|
||||
+4
-4
@@ -76,7 +76,7 @@ export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props)
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
||||
"rounded-md bg-mineshaft-800 px-10 py-6",
|
||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
||||
)}
|
||||
>
|
||||
@@ -84,8 +84,8 @@ export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props)
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faMoneyBill} className="text-4xl" />
|
||||
</div>
|
||||
<div className="flex-grow flex flex-col">
|
||||
<div className="font-medium mb-1 text-lg">Project</div>
|
||||
<div className="flex flex-grow flex-col">
|
||||
<div className="mb-1 text-lg font-medium">Project</div>
|
||||
<div className="text-xs font-light">
|
||||
View and create new projects in this organization
|
||||
</div>
|
||||
@@ -107,7 +107,7 @@ export const WorkspacePermission = ({ isNonEditable, setValue, control }: Props)
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
||||
className="grid auto-cols-min grid-flow-col gap-8 overflow-hidden"
|
||||
>
|
||||
{isCustom &&
|
||||
PERMISSIONS.map(({ action, label }) => (
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export { OrgIdentityTab } from "./OrgIdentityTab";
|
||||
export { OrgMembersTab } from "./OrgMembersTab";
|
||||
export { OrgRoleTabSection } from "./OrgRoleTabSection";
|
||||
export { OrgRoleTabSection } from "./OrgRoleTabSection";
|
||||
|
||||
@@ -6,7 +6,7 @@ import { CreateOrgModal } from "../components";
|
||||
|
||||
export const NonePage = () => {
|
||||
const { popUp, handlePopUpToggle } = usePopUp(["createOrg"] as const);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
handlePopUpToggle("createOrg", true);
|
||||
}, []);
|
||||
@@ -19,4 +19,4 @@ export const NonePage = () => {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { NonePage } from "./NonePage";
|
||||
export { NonePage } from "./NonePage";
|
||||
|
||||
@@ -6,9 +6,9 @@ import { LogsSection } from "./components";
|
||||
export const AuditLogsPage = withProjectPermission(
|
||||
() => {
|
||||
return (
|
||||
<div className="flex justify-center bg-bunker-800 text-white w-full h-full">
|
||||
<div className="max-w-7xl px-6 w-full">
|
||||
<div className="py-6 sticky top-0 z-10 bg-bunker-800">
|
||||
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl px-6">
|
||||
<div className="sticky top-0 z-10 bg-bunker-800 py-6">
|
||||
<p className="text-3xl font-semibold text-gray-200">Audit Logs</p>
|
||||
<div />
|
||||
</div>
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { LogsSection } from "./LogsSection";
|
||||
export { LogsSection } from "./LogsSection";
|
||||
|
||||
@@ -2,25 +2,25 @@ import * as yup from "yup";
|
||||
|
||||
import { EventType, UserAgentType } from "@app/hooks/api/auditLogs/enums";
|
||||
|
||||
export const auditLogFilterFormSchema = yup.object({
|
||||
eventType: yup.string()
|
||||
.oneOf(Object.values(EventType), "Invalid event type"),
|
||||
export const auditLogFilterFormSchema = yup
|
||||
.object({
|
||||
eventType: yup.string().oneOf(Object.values(EventType), "Invalid event type"),
|
||||
actor: yup.string(),
|
||||
userAgentType: yup.string()
|
||||
.oneOf(Object.values(UserAgentType), "Invalid user agent type"),
|
||||
userAgentType: yup.string().oneOf(Object.values(UserAgentType), "Invalid user agent type"),
|
||||
startDate: yup.date(),
|
||||
endDate: yup.date().min(yup.ref("startDate"), "End date cannot be before start date"),
|
||||
page: yup.number(),
|
||||
perPage: yup.number()
|
||||
}).required();
|
||||
})
|
||||
.required();
|
||||
|
||||
export type AuditLogFilterFormData = yup.InferType<typeof auditLogFilterFormSchema>;
|
||||
|
||||
export type SetValueType = (
|
||||
name: keyof AuditLogFilterFormData,
|
||||
value: any,
|
||||
name: keyof AuditLogFilterFormData,
|
||||
value: any,
|
||||
options?: {
|
||||
shouldValidate?: boolean,
|
||||
shouldDirty?: boolean
|
||||
shouldValidate?: boolean;
|
||||
shouldDirty?: boolean;
|
||||
}
|
||||
) => void;
|
||||
) => void;
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { AuditLogsPage } from "./AuditLogsPage";
|
||||
export { AuditLogsPage } from "./AuditLogsPage";
|
||||
|
||||
@@ -6,8 +6,8 @@ import { IPAllowlistSection } from "./components";
|
||||
export const IPAllowlistPage = withProjectPermission(
|
||||
() => {
|
||||
return (
|
||||
<div className="flex justify-center bg-bunker-800 text-white w-full h-full">
|
||||
<div className="max-w-7xl px-6 w-full">
|
||||
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl px-6">
|
||||
<div className="my-6">
|
||||
<p className="text-3xl font-semibold text-gray-200">IP Allowlist</p>
|
||||
<div />
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { IPAllowlistSection } from "./IPAllowlistSection";
|
||||
export { IPAllowlistSection } from "./IPAllowlistSection";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { IPAllowlistPage } from "./IPAllowlistPage";
|
||||
export { IPAllowlistPage } from "./IPAllowlistPage";
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import {
|
||||
IdentitySection,
|
||||
} from "./components";
|
||||
import { IdentitySection } from "./components";
|
||||
|
||||
export const IdentityTab = () => {
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-identity"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<IdentitySection />
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-identity"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<IdentitySection />
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
+9
-9
@@ -318,7 +318,7 @@ export const IdentityRoles = ({
|
||||
icon={faClock}
|
||||
className={twMerge(
|
||||
new Date() > new Date(temporaryAccessEndTime as string) &&
|
||||
"text-red-600"
|
||||
"text-red-600"
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -391,14 +391,14 @@ export const IdentityRoles = ({
|
||||
defaultValue={
|
||||
userProjectRoleDetails?.isTemporary
|
||||
? {
|
||||
isTemporary: true,
|
||||
temporaryAccessStartTime:
|
||||
userProjectRoleDetails.temporaryAccessStartTime as string,
|
||||
temporaryRange:
|
||||
userProjectRoleDetails.temporaryRange as string,
|
||||
temporaryAccessEndTime:
|
||||
userProjectRoleDetails.temporaryAccessEndTime
|
||||
}
|
||||
isTemporary: true,
|
||||
temporaryAccessStartTime:
|
||||
userProjectRoleDetails.temporaryAccessStartTime as string,
|
||||
temporaryRange:
|
||||
userProjectRoleDetails.temporaryRange as string,
|
||||
temporaryAccessEndTime:
|
||||
userProjectRoleDetails.temporaryAccessEndTime
|
||||
}
|
||||
: false
|
||||
}
|
||||
render={({ field }) => (
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export { IdentitySection } from "./IdentitySection";
|
||||
export { IdentitySection } from "./IdentitySection";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { IdentitySection } from "./IdentitySection";
|
||||
export { IdentitySection } from "./IdentitySection";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { IdentityTab } from "./IdentityTab";
|
||||
export { IdentityTab } from "./IdentityTab";
|
||||
|
||||
@@ -71,7 +71,7 @@ export const MemberListTab = () => {
|
||||
const { data: wsKey } = useGetUserWsKey(workspaceId);
|
||||
const { data: members, isLoading: isMembersLoading } = useGetWorkspaceUsers(workspaceId);
|
||||
const { data: orgUsers } = useGetOrgUsers(orgId);
|
||||
|
||||
|
||||
const [searchMemberFilter, setSearchMemberFilter] = useState("");
|
||||
|
||||
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
|
||||
@@ -105,7 +105,8 @@ export const MemberListTab = () => {
|
||||
const orgUser = (orgUsers || []).find(({ id }) => id === orgMembershipId);
|
||||
if (!orgUser) return;
|
||||
|
||||
try { // TODO: update
|
||||
try {
|
||||
// TODO: update
|
||||
if (currentWorkspace.version === ProjectVersion.V1) {
|
||||
await addUserToWorkspace({
|
||||
workspaceId,
|
||||
@@ -236,14 +237,14 @@ export const MemberListTab = () => {
|
||||
a={ProjectPermissionSub.Member}
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<MemberRoles
|
||||
roles={roles}
|
||||
disableEdit={u.id === user?.id || !isAllowed}
|
||||
onOpenUpgradeModal={(description) =>
|
||||
handlePopUpOpen("upgradePlan", { description })
|
||||
}
|
||||
membershipId={membershipId}
|
||||
/>
|
||||
<MemberRoles
|
||||
roles={roles}
|
||||
disableEdit={u.id === user?.id || !isAllowed}
|
||||
onOpenUpgradeModal={(description) =>
|
||||
handlePopUpOpen("upgradePlan", { description })
|
||||
}
|
||||
membershipId={membershipId}
|
||||
/>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
</Td>
|
||||
|
||||
@@ -325,14 +325,12 @@ export const MemberRoles = ({
|
||||
<div>{formatRoleName(role, customRoleName)}</div>
|
||||
{isTemporary && (
|
||||
<div>
|
||||
<Tooltip
|
||||
content={isExpired ? "Access expired" : "Temporary access"}
|
||||
>
|
||||
<Tooltip content={isExpired ? "Access expired" : "Temporary access"}>
|
||||
<FontAwesomeIcon
|
||||
icon={faClock}
|
||||
className={twMerge(
|
||||
new Date() > new Date(temporaryAccessEndTime as string) &&
|
||||
"text-red-600"
|
||||
"text-red-600"
|
||||
)}
|
||||
/>
|
||||
</Tooltip>
|
||||
@@ -405,14 +403,14 @@ export const MemberRoles = ({
|
||||
defaultValue={
|
||||
userProjectRoleDetails?.isTemporary
|
||||
? {
|
||||
isTemporary: true,
|
||||
temporaryAccessStartTime:
|
||||
userProjectRoleDetails.temporaryAccessStartTime as string,
|
||||
temporaryRange:
|
||||
userProjectRoleDetails.temporaryRange as string,
|
||||
temporaryAccessEndTime:
|
||||
userProjectRoleDetails.temporaryAccessEndTime
|
||||
}
|
||||
isTemporary: true,
|
||||
temporaryAccessStartTime:
|
||||
userProjectRoleDetails.temporaryAccessStartTime as string,
|
||||
temporaryRange:
|
||||
userProjectRoleDetails.temporaryRange as string,
|
||||
temporaryAccessEndTime:
|
||||
userProjectRoleDetails.temporaryAccessEndTime
|
||||
}
|
||||
: false
|
||||
}
|
||||
render={({ field }) => (
|
||||
|
||||
+4
-4
@@ -91,7 +91,7 @@ export const SecretRollbackPermission = ({ isNonEditable, setValue, control }: P
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
||||
"rounded-md bg-mineshaft-800 px-10 py-6",
|
||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
||||
)}
|
||||
>
|
||||
@@ -99,8 +99,8 @@ export const SecretRollbackPermission = ({ isNonEditable, setValue, control }: P
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faPuzzlePiece} className="text-4xl" />
|
||||
</div>
|
||||
<div className="flex-grow flex flex-col">
|
||||
<div className="font-medium mb-1 text-lg">Secret Rollback</div>
|
||||
<div className="flex flex-grow flex-col">
|
||||
<div className="mb-1 text-lg font-medium">Secret Rollback</div>
|
||||
<div className="text-xs font-light">Secret rollback control actions</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -120,7 +120,7 @@ export const SecretRollbackPermission = ({ isNonEditable, setValue, control }: P
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
||||
className="grid auto-cols-min grid-flow-col gap-8 overflow-hidden"
|
||||
>
|
||||
{isCustom &&
|
||||
PERMISSIONS.map(({ action, label }) => (
|
||||
|
||||
+4
-4
@@ -135,7 +135,7 @@ export const SingleProjectPermission = ({
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
||||
"rounded-md bg-mineshaft-800 px-10 py-6",
|
||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
||||
)}
|
||||
>
|
||||
@@ -143,8 +143,8 @@ export const SingleProjectPermission = ({
|
||||
<div>
|
||||
<FontAwesomeIcon icon={icon} className="text-4xl" />
|
||||
</div>
|
||||
<div className="flex-grow flex flex-col">
|
||||
<div className="font-medium mb-1 text-lg">{title}</div>
|
||||
<div className="flex flex-grow flex-col">
|
||||
<div className="mb-1 text-lg font-medium">{title}</div>
|
||||
<div className="text-xs font-light">{subtitle}</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -164,7 +164,7 @@ export const SingleProjectPermission = ({
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
||||
className="grid auto-cols-min grid-flow-col gap-8 overflow-hidden"
|
||||
>
|
||||
{isCustom &&
|
||||
getPermissionList(formName).map(({ action, label }) => (
|
||||
|
||||
+4
-4
@@ -71,7 +71,7 @@ export const WsProjectPermission = ({ isNonEditable, setValue, control }: Props)
|
||||
return (
|
||||
<div
|
||||
className={twMerge(
|
||||
"px-10 py-6 bg-mineshaft-800 rounded-md",
|
||||
"rounded-md bg-mineshaft-800 px-10 py-6",
|
||||
selectedPermissionCategory !== Permission.NoAccess && "border-l-2 border-primary-600"
|
||||
)}
|
||||
>
|
||||
@@ -79,8 +79,8 @@ export const WsProjectPermission = ({ isNonEditable, setValue, control }: Props)
|
||||
<div>
|
||||
<FontAwesomeIcon icon={faPuzzlePiece} className="text-4xl" />
|
||||
</div>
|
||||
<div className="flex-grow flex flex-col">
|
||||
<div className="font-medium mb-1 text-lg">Project</div>
|
||||
<div className="flex flex-grow flex-col">
|
||||
<div className="mb-1 text-lg font-medium">Project</div>
|
||||
<div className="text-xs font-light">Project control actions</div>
|
||||
</div>
|
||||
<div>
|
||||
@@ -99,7 +99,7 @@ export const WsProjectPermission = ({ isNonEditable, setValue, control }: Props)
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{ height: isCustom ? "2.5rem" : 0, paddingTop: isCustom ? "1rem" : 0 }}
|
||||
className="overflow-hidden grid gap-8 grid-flow-col auto-cols-min"
|
||||
className="grid auto-cols-min grid-flow-col gap-8 overflow-hidden"
|
||||
>
|
||||
{isCustom &&
|
||||
PERMISSIONS.map(({ action, label }) => (
|
||||
|
||||
+13
-15
@@ -1,19 +1,17 @@
|
||||
import { motion } from "framer-motion";
|
||||
|
||||
import {
|
||||
ServiceTokenSection,
|
||||
} from "./components";
|
||||
import { ServiceTokenSection } from "./components";
|
||||
|
||||
export const ServiceTokenTab = () => {
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-service-token"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<ServiceTokenSection />
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<motion.div
|
||||
key="panel-service-token"
|
||||
transition={{ duration: 0.15 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
>
|
||||
<ServiceTokenSection />
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -161,7 +161,7 @@ export const AddServiceTokenModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
const axiosError = err as AxiosError
|
||||
const axiosError = err as AxiosError;
|
||||
if (axiosError?.response?.status === 401) {
|
||||
createNotification({
|
||||
text: "You do not have access to the selected environment/path",
|
||||
|
||||
+1
-3
@@ -49,9 +49,7 @@ export const ServiceTokenSection = withProjectPermission(
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<div className="mb-2 flex justify-between">
|
||||
<p className="text-xl font-semibold text-mineshaft-100">
|
||||
Service Tokens
|
||||
</p>
|
||||
<p className="text-xl font-semibold text-mineshaft-100">Service Tokens</p>
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Create}
|
||||
a={ProjectPermissionSub.ServiceTokens}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export {ServiceTokenSection} from "./ServiceTokenSection"
|
||||
export { ServiceTokenSection } from "./ServiceTokenSection";
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export { ServiceTokenSection } from "./ServiceTokenSection";
|
||||
export { ServiceTokenSection } from "./ServiceTokenSection";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { ServiceTokenTab } from "./ServiceTokenTab";
|
||||
export { ServiceTokenTab } from "./ServiceTokenTab";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export { IdentityTab } from "./IdentityTab";
|
||||
export { MemberListTab } from "./MemberListTab";
|
||||
export { ProjectRoleListTab } from "./ProjectRoleListTab";
|
||||
export { ServiceTokenTab } from "./ServiceTokenTab";
|
||||
export { ServiceTokenTab } from "./ServiceTokenTab";
|
||||
|
||||
+1
-1
@@ -151,7 +151,7 @@ export const SecretApprovalRequestChangeItem = ({
|
||||
</Td>
|
||||
<Td>
|
||||
{(op === CommitType.CREATE ? newVersion?.tags : secretVersion?.tags)?.map(
|
||||
({ name, id: tagId, color}) => (
|
||||
({ name, id: tagId, color }) => (
|
||||
<Tag
|
||||
className="flex w-min items-center space-x-2"
|
||||
key={`${
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { CreateSecretForm } from "./CreateSecretForm"
|
||||
export { CreateSecretForm } from "./CreateSecretForm";
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export { ProjectIndexSecretsSection } from "./ProjectIndexSecretsSection";
|
||||
export { ProjectIndexSecretsSection } from "./ProjectIndexSecretsSection";
|
||||
|
||||
+1
-1
@@ -87,7 +87,7 @@ export const SecretEditRow = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="group flex w-full cursor-text space-x-2 items-center">
|
||||
<div className="group flex w-full cursor-text items-center space-x-2">
|
||||
<div className="flex-grow border-r border-r-mineshaft-600 pr-2 pl-1">
|
||||
<Controller
|
||||
control={control}
|
||||
|
||||
+1
-1
@@ -90,7 +90,7 @@ export const CreateRotationForm = ({
|
||||
<ModalContent
|
||||
title={`Secret rotation for ${provider.name}`}
|
||||
subTitle="Provide the required inputs needed for the rotation"
|
||||
className="max-w-2xl max-h-screen overflow-scroll my-4"
|
||||
className="my-4 max-h-screen max-w-2xl overflow-scroll"
|
||||
>
|
||||
<Stepper activeStep={wizardStep} direction="horizontal" className="mb-4">
|
||||
{WIZARD_STEPS.map(({ title, description }, index) => (
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ export const RotationInputForm = ({ onSubmit, onCancel, inputSchema }: Props) =>
|
||||
key={`provider-input-${inputName}`}
|
||||
label={
|
||||
<div className="flex items-center space-x-2">
|
||||
<FormLabel className="uppercase mb-0" label={inputName.replaceAll("_", " ")} />
|
||||
<FormLabel className="mb-0 uppercase" label={inputName.replaceAll("_", " ")} />
|
||||
{Boolean(inputSchema.properties[inputName]?.desc) && (
|
||||
<Tooltip
|
||||
className="max-w-xs"
|
||||
|
||||
@@ -32,7 +32,7 @@ export const RiskStatusSelection = ({
|
||||
disabled={!isAllowed}
|
||||
value={selectedRiskStatus}
|
||||
onChange={(e) => setSelectedRiskStatus(e.target.value)}
|
||||
className="block w-full py-2 px-3 rounded-md shadow-sm focus:outline-none focus:ring-blue-500 focus:border-blue-500"
|
||||
className="block w-full rounded-md py-2 px-3 shadow-sm focus:border-blue-500 focus:outline-none focus:ring-blue-500"
|
||||
>
|
||||
<option>Unresolved</option>
|
||||
<option value={RiskStatus.RESOLVED_FALSE_POSITIVE}>
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { SecretScanningLogsTable } from "./SecretScanningLogsTable";
|
||||
export { SecretScanningLogsTable } from "./SecretScanningLogsTable";
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import {
|
||||
BillingTabGroup
|
||||
} from "./components";
|
||||
import { BillingTabGroup } from "./components";
|
||||
|
||||
export const BillingSettingsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex justify-center bg-bunker-800 text-white w-full h-full">
|
||||
<div className="max-w-7xl px-6 w-full">
|
||||
<div className="my-6">
|
||||
<p className="text-3xl font-semibold text-gray-200">{t("billing.title")}</p>
|
||||
<div />
|
||||
</div>
|
||||
<BillingTabGroup />
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className="flex h-full w-full justify-center bg-bunker-800 text-white">
|
||||
<div className="w-full max-w-7xl px-6">
|
||||
<div className="my-6">
|
||||
<p className="text-3xl font-semibold text-gray-200">{t("billing.title")}</p>
|
||||
<div />
|
||||
</div>
|
||||
<BillingTabGroup />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+7
-7
@@ -2,10 +2,10 @@ import { CurrentPlanSection } from "./CurrentPlanSection";
|
||||
import { PreviewSection } from "./PreviewSection";
|
||||
|
||||
export const BillingCloudTab = () => {
|
||||
return (
|
||||
<div>
|
||||
<PreviewSection />
|
||||
<CurrentPlanSection />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<PreviewSection />
|
||||
<CurrentPlanSection />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+51
-53
@@ -1,10 +1,7 @@
|
||||
import { Fragment } from "react"
|
||||
import { Tab } from "@headlessui/react"
|
||||
import { Fragment } from "react";
|
||||
import { Tab } from "@headlessui/react";
|
||||
|
||||
import {
|
||||
Modal,
|
||||
ModalContent
|
||||
} from "@app/components/v2";
|
||||
import { Modal, ModalContent } from "@app/components/v2";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
import { ManagePlansTable } from "./ManagePlansTable";
|
||||
@@ -14,51 +11,52 @@ type Props = {
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["managePlan"]>, state?: boolean) => void;
|
||||
};
|
||||
|
||||
export const ManagePlansModal = ({
|
||||
popUp,
|
||||
handlePopUpToggle
|
||||
}: Props) => {
|
||||
return (
|
||||
export const ManagePlansModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.managePlan?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("managePlan", isOpen);
|
||||
}}
|
||||
>
|
||||
<ModalContent className="max-w-screen-lg" title="Infisical Cloud Plans">
|
||||
<Tab.Group>
|
||||
<Tab.List className="border-b-2 border-mineshaft-600 max-w-screen-lg">
|
||||
<Tab as={Fragment}>
|
||||
{({ selected }) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`p-4 ${selected ? "border-b-2 border-white text-white" : "text-mineshaft-400"} w-30 font-semibold outline-none`}
|
||||
>
|
||||
Bill monthly
|
||||
</button>
|
||||
)}
|
||||
</Tab>
|
||||
<Tab as={Fragment}>
|
||||
{({ selected }) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`p-4 ${selected ? "border-b-2 border-white text-white" : "text-mineshaft-400"} w-30 font-semibold outline-none`}
|
||||
>
|
||||
Bill yearly
|
||||
</button>
|
||||
)}
|
||||
</Tab>
|
||||
</Tab.List>
|
||||
<Tab.Panels className="mt-4">
|
||||
<Tab.Panel>
|
||||
<ManagePlansTable billingCycle="monthly" />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<ManagePlansTable billingCycle="yearly" />
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
isOpen={popUp?.managePlan?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("managePlan", isOpen);
|
||||
}}
|
||||
>
|
||||
<ModalContent className="max-w-screen-lg" title="Infisical Cloud Plans">
|
||||
<Tab.Group>
|
||||
<Tab.List className="max-w-screen-lg border-b-2 border-mineshaft-600">
|
||||
<Tab as={Fragment}>
|
||||
{({ selected }) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`p-4 ${
|
||||
selected ? "border-b-2 border-white text-white" : "text-mineshaft-400"
|
||||
} w-30 font-semibold outline-none`}
|
||||
>
|
||||
Bill monthly
|
||||
</button>
|
||||
)}
|
||||
</Tab>
|
||||
<Tab as={Fragment}>
|
||||
{({ selected }) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`p-4 ${
|
||||
selected ? "border-b-2 border-white text-white" : "text-mineshaft-400"
|
||||
} w-30 font-semibold outline-none`}
|
||||
>
|
||||
Bill yearly
|
||||
</button>
|
||||
)}
|
||||
</Tab>
|
||||
</Tab.List>
|
||||
<Tab.Panels className="mt-4">
|
||||
<Tab.Panel>
|
||||
<ManagePlansTable billingCycle="monthly" />
|
||||
</Tab.Panel>
|
||||
<Tab.Panel>
|
||||
<ManagePlansTable billingCycle="yearly" />
|
||||
</Tab.Panel>
|
||||
</Tab.Panels>
|
||||
</Tab.Group>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export { BillingCloudTab } from "./BillingCloudTab";
|
||||
export { BillingCloudTab } from "./BillingCloudTab";
|
||||
|
||||
+9
-9
@@ -4,12 +4,12 @@ import { PmtMethodsSection } from "./PmtMethodsSection";
|
||||
import { TaxIDSection } from "./TaxIDSection";
|
||||
|
||||
export const BillingDetailsTab = () => {
|
||||
return (
|
||||
<>
|
||||
<CompanyNameSection />
|
||||
<InvoiceEmailSection />
|
||||
<PmtMethodsSection />
|
||||
<TaxIDSection />
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<CompanyNameSection />
|
||||
<InvoiceEmailSection />
|
||||
<PmtMethodsSection />
|
||||
<TaxIDSection />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+3
-3
@@ -15,9 +15,9 @@ export const TaxIDSection = () => {
|
||||
] 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>
|
||||
<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">Tax ID</h2>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Billing}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export { BillingDetailsTab } from "./BillingDetailsTab";
|
||||
export { BillingDetailsTab } from "./BillingDetailsTab";
|
||||
|
||||
+7
-7
@@ -1,10 +1,10 @@
|
||||
import { InvoicesTable } from "./InvoicesTable";
|
||||
|
||||
export const BillingReceiptsTab = () => {
|
||||
return (
|
||||
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
||||
<h2 className="text-xl font-semibold flex-1 text-white">Invoices</h2>
|
||||
<InvoicesTable />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<h2 className="flex-1 text-xl font-semibold text-white">Invoices</h2>
|
||||
<InvoicesTable />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export { BillingReceiptsTab } from "./BillingReceiptsTab";
|
||||
export { BillingReceiptsTab } from "./BillingReceiptsTab";
|
||||
|
||||
+6
-6
@@ -1,9 +1,9 @@
|
||||
import { LicensesSection } from "./LicensesSection";
|
||||
|
||||
export const BillingSelfHostedTab = () => {
|
||||
return (
|
||||
<div>
|
||||
<LicensesSection />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<LicensesSection />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export * from "./BillingSelfHostedTab";
|
||||
export * from "./BillingSelfHostedTab";
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ export const BillingTabGroup = withPermission(
|
||||
{({ selected }) => (
|
||||
<button
|
||||
type="button"
|
||||
className={`w-30 py-2 mx-2 mr-4 font-medium text-sm outline-none ${
|
||||
className={`w-30 mx-2 mr-4 py-2 text-sm font-medium outline-none ${
|
||||
selected ? "border-b border-white text-white" : "text-mineshaft-400"
|
||||
}`}
|
||||
>
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
export { BillingTabGroup } from "./BillingTabGroup";
|
||||
export { BillingTabGroup } from "./BillingTabGroup";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { BillingTabGroup } from "./BillingTabGroup";
|
||||
export { BillingTabGroup } from "./BillingTabGroup";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { BillingSettingsPage } from "./BillingSettingsPage";
|
||||
export { BillingSettingsPage } from "./BillingSettingsPage";
|
||||
|
||||
@@ -6,13 +6,13 @@ export const OrgSettingsPage = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="flex justify-center bg-bunker-800 text-white w-full py-6">
|
||||
<div className="max-w-4xl w-full px-6">
|
||||
<div className="mb-4">
|
||||
<p className="text-3xl font-semibold text-gray-200">{t("settings.org.title")}</p>
|
||||
</div>
|
||||
<OrgTabGroup />
|
||||
</div>
|
||||
<div className="flex w-full justify-center bg-bunker-800 py-6 text-white">
|
||||
<div className="w-full max-w-4xl px-6">
|
||||
<div className="mb-4">
|
||||
<p className="text-3xl font-semibold text-gray-200">{t("settings.org.title")}</p>
|
||||
</div>
|
||||
<OrgTabGroup />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,31 +1,20 @@
|
||||
import { useEffect } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input,
|
||||
Modal,
|
||||
ModalContent,
|
||||
TextArea
|
||||
} from "@app/components/v2";
|
||||
import { Button, FormControl, Input, Modal, ModalContent, TextArea } from "@app/components/v2";
|
||||
import { useOrganization } from "@app/context";
|
||||
import {
|
||||
useCreateLDAPConfig,
|
||||
useGetLDAPConfig,
|
||||
useUpdateLDAPConfig
|
||||
} from "@app/hooks/api";
|
||||
import { useCreateLDAPConfig, useGetLDAPConfig, useUpdateLDAPConfig } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const LDAPFormSchema = z.object({
|
||||
url: z.string().default(""),
|
||||
bindDN: z.string().default(""),
|
||||
bindPass: z.string().default(""),
|
||||
searchBase: z.string().default(""),
|
||||
caCert: z.string().optional()
|
||||
url: z.string().default(""),
|
||||
bindDN: z.string().default(""),
|
||||
bindPass: z.string().default(""),
|
||||
searchBase: z.string().default(""),
|
||||
caCert: z.string().optional()
|
||||
});
|
||||
|
||||
export type TLDAPFormData = z.infer<typeof LDAPFormSchema>;
|
||||
@@ -36,194 +25,152 @@ type Props = {
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addLDAP"]>, state?: boolean) => void;
|
||||
};
|
||||
|
||||
export const LDAPModal = ({
|
||||
popUp,
|
||||
handlePopUpClose,
|
||||
handlePopUpToggle
|
||||
}: Props) => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateLDAPConfig();
|
||||
const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateLDAPConfig();
|
||||
const { data } = useGetLDAPConfig(currentOrg?.id?? "");
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
} = useForm<TLDAPFormData>({
|
||||
resolver: zodResolver(LDAPFormSchema)
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
reset({
|
||||
url: data?.url ?? "",
|
||||
bindDN: data?.bindDN ?? "",
|
||||
bindPass: data?.bindPass ?? "",
|
||||
searchBase: data?.searchBase ?? "",
|
||||
caCert: data?.caCert ?? ""
|
||||
});
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
const onSSOModalSubmit = async ({
|
||||
url,
|
||||
bindDN,
|
||||
bindPass,
|
||||
searchBase,
|
||||
caCert
|
||||
}: TLDAPFormData) => {
|
||||
try {
|
||||
if (!currentOrg) return;
|
||||
|
||||
if (!data) {
|
||||
await createMutateAsync({
|
||||
organizationId: currentOrg.id,
|
||||
isActive: false,
|
||||
url,
|
||||
bindDN,
|
||||
bindPass,
|
||||
searchBase,
|
||||
caCert
|
||||
});
|
||||
} else {
|
||||
await updateMutateAsync({
|
||||
organizationId: currentOrg.id,
|
||||
isActive: false,
|
||||
url,
|
||||
bindDN,
|
||||
bindPass,
|
||||
searchBase,
|
||||
caCert
|
||||
});
|
||||
}
|
||||
export const LDAPModal = ({ popUp, handlePopUpClose, handlePopUpToggle }: Props) => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { mutateAsync: createMutateAsync, isLoading: createIsLoading } = useCreateLDAPConfig();
|
||||
const { mutateAsync: updateMutateAsync, isLoading: updateIsLoading } = useUpdateLDAPConfig();
|
||||
const { data } = useGetLDAPConfig(currentOrg?.id ?? "");
|
||||
|
||||
handlePopUpClose("addLDAP");
|
||||
const { control, handleSubmit, reset } = useForm<TLDAPFormData>({
|
||||
resolver: zodResolver(LDAPFormSchema)
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${!data ? "added" : "updated"} LDAP configuration`,
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: `Failed to ${!data ? "add" : "update"} LDAP configuration`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
reset({
|
||||
url: data?.url ?? "",
|
||||
bindDN: data?.bindDN ?? "",
|
||||
bindPass: data?.bindPass ?? "",
|
||||
searchBase: data?.searchBase ?? "",
|
||||
caCert: data?.caCert ?? ""
|
||||
});
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.addLDAP?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("addLDAP", isOpen);
|
||||
reset();
|
||||
}}
|
||||
>
|
||||
<ModalContent title="Manage LDAP configuration">
|
||||
<form onSubmit={handleSubmit(onSSOModalSubmit)}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="url"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="URL"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="ldaps://ldap.myorg.com:636"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="bindDN"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Bind DN"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="cn=infisical,ou=Users,dc=example,dc=com"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="bindPass"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Bind Pass"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
type="password"
|
||||
placeholder="********"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="searchBase"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Search Base / User DN"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="ou=people,dc=acme,dc=com"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="caCert"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="CA 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("addLDAP")}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
const onSSOModalSubmit = async ({ url, bindDN, bindPass, searchBase, caCert }: TLDAPFormData) => {
|
||||
try {
|
||||
if (!currentOrg) return;
|
||||
|
||||
if (!data) {
|
||||
await createMutateAsync({
|
||||
organizationId: currentOrg.id,
|
||||
isActive: false,
|
||||
url,
|
||||
bindDN,
|
||||
bindPass,
|
||||
searchBase,
|
||||
caCert
|
||||
});
|
||||
} else {
|
||||
await updateMutateAsync({
|
||||
organizationId: currentOrg.id,
|
||||
isActive: false,
|
||||
url,
|
||||
bindDN,
|
||||
bindPass,
|
||||
searchBase,
|
||||
caCert
|
||||
});
|
||||
}
|
||||
|
||||
handlePopUpClose("addLDAP");
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${!data ? "added" : "updated"} LDAP configuration`,
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: `Failed to ${!data ? "add" : "update"} LDAP configuration`,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.addLDAP?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("addLDAP", isOpen);
|
||||
reset();
|
||||
}}
|
||||
>
|
||||
<ModalContent title="Manage LDAP configuration">
|
||||
<form onSubmit={handleSubmit(onSSOModalSubmit)}>
|
||||
<Controller
|
||||
control={control}
|
||||
name="url"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="URL" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Input {...field} placeholder="ldaps://ldap.myorg.com:636" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="bindDN"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Bind DN" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Input {...field} placeholder="cn=infisical,ou=Users,dc=example,dc=com" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="bindPass"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="Bind Pass" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Input {...field} type="password" placeholder="********" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="searchBase"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Search Base / User DN"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Input {...field} placeholder="ou=people,dc=acme,dc=com" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="caCert"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="CA 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("addLDAP")}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
+74
-78
@@ -1,66 +1,60 @@
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import { Switch, UpgradePlanModal } from "@app/components/v2";
|
||||
import {
|
||||
Switch,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrganization,
|
||||
useSubscription
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrganization,
|
||||
useSubscription
|
||||
} from "@app/context";
|
||||
import { useLogoutUser, useUpdateOrg } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
export const OrgGeneralAuthSection = () => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { subscription } = useSubscription();
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const { mutateAsync } = useUpdateOrg();
|
||||
|
||||
const logout = useLogoutUser();
|
||||
|
||||
const handleEnforceOrgAuthToggle = async (value: boolean) => {
|
||||
try {
|
||||
if (!currentOrg?.id) return;
|
||||
if (!subscription?.samlSSO) {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
return;
|
||||
}
|
||||
|
||||
await mutateAsync({
|
||||
orgId: currentOrg?.id,
|
||||
authEnforced: value
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${value ? "enforced" : "un-enforced"} org-level auth`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
if (value) {
|
||||
await logout.mutateAsync();
|
||||
window.open(`/api/v1/sso/redirect/saml2/organizations/${currentOrg.slug}`);
|
||||
window.close();
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: (err as { response: { data: { message: string; }}}).response.data.message,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
}
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { subscription } = useSubscription();
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp(["upgradePlan"] as const);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <div className="py-4">
|
||||
const { mutateAsync } = useUpdateOrg();
|
||||
|
||||
const logout = useLogoutUser();
|
||||
|
||||
const handleEnforceOrgAuthToggle = async (value: boolean) => {
|
||||
try {
|
||||
if (!currentOrg?.id) return;
|
||||
if (!subscription?.samlSSO) {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
return;
|
||||
}
|
||||
|
||||
await mutateAsync({
|
||||
orgId: currentOrg?.id,
|
||||
authEnforced: value
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${value ? "enforced" : "un-enforced"} org-level auth`,
|
||||
type: "success"
|
||||
});
|
||||
|
||||
if (value) {
|
||||
await logout.mutateAsync();
|
||||
window.open(`/api/v1/sso/redirect/saml2/organizations/${currentOrg.slug}`);
|
||||
window.close();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: (err as { response: { data: { message: string } } }).response.data.message,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* <div className="py-4">
|
||||
<div className="mb-2 flex justify-between">
|
||||
<h3 className="text-md text-mineshaft-100">Allow users to send invites</h3>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
|
||||
@@ -76,27 +70,29 @@ export const OrgGeneralAuthSection = () => {
|
||||
</div>
|
||||
<p className="text-sm text-mineshaft-300">Allow members to invite new users to this organization</p>
|
||||
</div> */}
|
||||
<div className="py-4">
|
||||
<div className="mb-2 flex justify-between">
|
||||
<h3 className="text-md text-mineshaft-100">Enforce SAML SSO</h3>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
|
||||
{(isAllowed) => (
|
||||
<Switch
|
||||
id="enforce-org-auth"
|
||||
onCheckedChange={(value) => handleEnforceOrgAuthToggle(value)}
|
||||
isChecked={currentOrg?.authEnforced ?? false}
|
||||
isDisabled={!isAllowed}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<p className="text-sm text-mineshaft-300">Enforce members to authenticate via SAML to access this organization</p>
|
||||
</div>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can enforce SAML SSO if you switch to Infisical's Pro plan."
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
<div className="py-4">
|
||||
<div className="mb-2 flex justify-between">
|
||||
<h3 className="text-md text-mineshaft-100">Enforce SAML SSO</h3>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
|
||||
{(isAllowed) => (
|
||||
<Switch
|
||||
id="enforce-org-auth"
|
||||
onCheckedChange={(value) => handleEnforceOrgAuthToggle(value)}
|
||||
isChecked={currentOrg?.authEnforced ?? false}
|
||||
isDisabled={!isAllowed}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<p className="text-sm text-mineshaft-300">
|
||||
Enforce members to authenticate via SAML to access this organization
|
||||
</p>
|
||||
</div>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can enforce SAML SSO if you switch to Infisical's Pro plan."
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+30
-40
@@ -1,21 +1,13 @@
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
Switch,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import { Button, Switch, UpgradePlanModal } from "@app/components/v2";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrganization,
|
||||
useSubscription
|
||||
} from "@app/context";
|
||||
import {
|
||||
useCreateLDAPConfig,
|
||||
useGetLDAPConfig,
|
||||
useUpdateLDAPConfig
|
||||
} from "@app/hooks/api";
|
||||
import { useCreateLDAPConfig, useGetLDAPConfig, useUpdateLDAPConfig } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { LDAPModal } from "./LDAPModal";
|
||||
@@ -30,7 +22,7 @@ export const OrgLDAPSection = (): JSX.Element => {
|
||||
"addLDAP",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
|
||||
const { mutateAsync: createMutateAsync } = useCreateLDAPConfig();
|
||||
|
||||
const handleLDAPToggle = async (value: boolean) => {
|
||||
@@ -71,7 +63,7 @@ export const OrgLDAPSection = (): JSX.Element => {
|
||||
url: "",
|
||||
bindDN: "",
|
||||
bindPass: "",
|
||||
searchBase: "",
|
||||
searchBase: ""
|
||||
});
|
||||
}
|
||||
|
||||
@@ -92,11 +84,7 @@ export const OrgLDAPSection = (): JSX.Element => {
|
||||
<h2 className="text-md text-mineshaft-100">LDAP</h2>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Ldap}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
onClick={addLDAPBtnClick}
|
||||
colorSchema="secondary"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<Button onClick={addLDAPBtnClick} colorSchema="secondary" isDisabled={!isAllowed}>
|
||||
Manage
|
||||
</Button>
|
||||
)}
|
||||
@@ -109,31 +97,33 @@ export const OrgLDAPSection = (): JSX.Element => {
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<h2 className="text-md text-mineshaft-100">Enable LDAP</h2>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Ldap}>
|
||||
{(isAllowed) => (
|
||||
<Switch
|
||||
id="enable-saml-sso"
|
||||
onCheckedChange={(value) => handleLDAPToggle(value)}
|
||||
isChecked={data ? data.isActive : false}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Enable
|
||||
</Switch>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
{(isAllowed) => (
|
||||
<Switch
|
||||
id="enable-saml-sso"
|
||||
onCheckedChange={(value) => handleLDAPToggle(value)}
|
||||
isChecked={data ? data.isActive : false}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Enable
|
||||
</Switch>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<p className="text-sm text-mineshaft-300">Allow members to authenticate into Infisical with LDAP</p>
|
||||
<p className="text-sm text-mineshaft-300">
|
||||
Allow members to authenticate into Infisical with LDAP
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<LDAPModal
|
||||
popUp={popUp}
|
||||
handlePopUpClose={handlePopUpClose}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can use LDAP authentication if you switch to Infisical's Enterprise plan."
|
||||
/>
|
||||
<LDAPModal
|
||||
popUp={popUp}
|
||||
handlePopUpClose={handlePopUpClose}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can use LDAP authentication if you switch to Infisical's Enterprise plan."
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
+102
-103
@@ -1,117 +1,116 @@
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import { OrgPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
Button,
|
||||
Switch,
|
||||
UpgradePlanModal
|
||||
} from "@app/components/v2";
|
||||
import { Button, Switch, UpgradePlanModal } from "@app/components/v2";
|
||||
import {
|
||||
OrgPermissionActions,
|
||||
OrgPermissionSubjects,
|
||||
useOrganization,
|
||||
useSubscription} from "@app/context";
|
||||
useSubscription
|
||||
} from "@app/context";
|
||||
import { useUpdateOrg } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
import { ScimTokenModal } from "./ScimTokenModal";
|
||||
|
||||
export const OrgScimSection = () => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { subscription } = useSubscription();
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"scimToken",
|
||||
"deleteScimToken",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
const { mutateAsync } = useUpdateOrg();
|
||||
|
||||
const addScimTokenBtnClick = () => {
|
||||
if (subscription?.scim) {
|
||||
handlePopUpOpen("scimToken");
|
||||
} else {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}
|
||||
}
|
||||
|
||||
const handleEnableSCIMToggle = async (value: boolean) => {
|
||||
try {
|
||||
if (!currentOrg?.id) return;
|
||||
if (!subscription?.scim) {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
return;
|
||||
}
|
||||
|
||||
await mutateAsync({
|
||||
orgId: currentOrg?.id,
|
||||
scimEnabled: value
|
||||
});
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { currentOrg } = useOrganization();
|
||||
const { subscription } = useSubscription();
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle } = usePopUp([
|
||||
"scimToken",
|
||||
"deleteScimToken",
|
||||
"upgradePlan"
|
||||
] as const);
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${value ? "enabled" : "disabled"} SCIM provisioning`,
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
createNotification({
|
||||
text: (err as { response: { data: { message: string; }}}).response.data.message,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
const { mutateAsync } = useUpdateOrg();
|
||||
|
||||
const addScimTokenBtnClick = () => {
|
||||
if (subscription?.scim) {
|
||||
handlePopUpOpen("scimToken");
|
||||
} else {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr className="border-mineshaft-600" />
|
||||
<div className="py-4">
|
||||
<div className="mb-2 flex justify-between items-center">
|
||||
<h2 className="text-md text-mineshaft-100">SCIM</h2>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Read} a={OrgPermissionSubjects.Scim}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
onClick={addScimTokenBtnClick}
|
||||
colorSchema="secondary"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Manage
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<p className="text-sm text-mineshaft-300">Manage SCIM configuration</p>
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<div className="mb-2 flex justify-between items-center">
|
||||
<h2 className="text-md text-mineshaft-100">Enable SCIM</h2>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Scim}>
|
||||
{(isAllowed) => (
|
||||
<Switch
|
||||
id="enable-scim"
|
||||
onCheckedChange={(value) => {
|
||||
if (subscription?.scim) {
|
||||
handleEnableSCIMToggle(value)
|
||||
} else {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}
|
||||
}}
|
||||
isChecked={currentOrg?.scimEnabled ?? false}
|
||||
isDisabled={!isAllowed}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<p className="text-sm text-mineshaft-300">Allow member provisioning/deprovisioning with SCIM</p>
|
||||
</div>
|
||||
<ScimTokenModal
|
||||
popUp={popUp}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can use SCIM Provisioning if you switch to Infisical's Enterprise plan."
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const handleEnableSCIMToggle = async (value: boolean) => {
|
||||
try {
|
||||
if (!currentOrg?.id) return;
|
||||
if (!subscription?.scim) {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
return;
|
||||
}
|
||||
|
||||
await mutateAsync({
|
||||
orgId: currentOrg?.id,
|
||||
scimEnabled: value
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: `Successfully ${value ? "enabled" : "disabled"} SCIM provisioning`,
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
createNotification({
|
||||
text: (err as { response: { data: { message: string } } }).response.data.message,
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<hr className="border-mineshaft-600" />
|
||||
<div className="py-4">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<h2 className="text-md text-mineshaft-100">SCIM</h2>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Read} a={OrgPermissionSubjects.Scim}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
onClick={addScimTokenBtnClick}
|
||||
colorSchema="secondary"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
Manage
|
||||
</Button>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<p className="text-sm text-mineshaft-300">Manage SCIM configuration</p>
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<h2 className="text-md text-mineshaft-100">Enable SCIM</h2>
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Scim}>
|
||||
{(isAllowed) => (
|
||||
<Switch
|
||||
id="enable-scim"
|
||||
onCheckedChange={(value) => {
|
||||
if (subscription?.scim) {
|
||||
handleEnableSCIMToggle(value);
|
||||
} else {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
}
|
||||
}}
|
||||
isChecked={currentOrg?.scimEnabled ?? false}
|
||||
isDisabled={!isAllowed}
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
</div>
|
||||
<p className="text-sm text-mineshaft-300">
|
||||
Allow member provisioning/deprovisioning with SCIM
|
||||
</p>
|
||||
</div>
|
||||
<ScimTokenModal
|
||||
popUp={popUp}
|
||||
handlePopUpOpen={handlePopUpOpen}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can use SCIM Provisioning if you switch to Infisical's Enterprise plan."
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+24
-26
@@ -28,7 +28,7 @@ export const OrgSSOSection = (): JSX.Element => {
|
||||
const handleSamlSSOToggle = async (value: boolean) => {
|
||||
try {
|
||||
if (!currentOrg?.id) return;
|
||||
|
||||
|
||||
if (!subscription?.samlSSO) {
|
||||
handlePopUpOpen("upgradePlan");
|
||||
return;
|
||||
@@ -86,11 +86,7 @@ export const OrgSSOSection = (): JSX.Element => {
|
||||
{!isLoading && (
|
||||
<OrgPermissionCan I={OrgPermissionActions.Create} a={OrgPermissionSubjects.Sso}>
|
||||
{(isAllowed) => (
|
||||
<Button
|
||||
onClick={addSSOBtnClick}
|
||||
colorSchema="secondary"
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<Button onClick={addSSOBtnClick} colorSchema="secondary" isDisabled={!isAllowed}>
|
||||
Manage
|
||||
</Button>
|
||||
)}
|
||||
@@ -98,35 +94,37 @@ export const OrgSSOSection = (): JSX.Element => {
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-mineshaft-300">Manage SAML authentication configuration</p>
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<h2 className="text-md text-mineshaft-100">Enable SAML</h2>
|
||||
{!isLoading && (
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<h2 className="text-md text-mineshaft-100">Enable SAML</h2>
|
||||
{!isLoading && (
|
||||
<OrgPermissionCan I={OrgPermissionActions.Edit} a={OrgPermissionSubjects.Sso}>
|
||||
{(isAllowed) => (
|
||||
<Switch
|
||||
id="enable-saml-sso"
|
||||
onCheckedChange={(value) => handleSamlSSOToggle(value)}
|
||||
isChecked={data ? data.isActive : false}
|
||||
isDisabled={!isAllowed}
|
||||
/>
|
||||
/>
|
||||
)}
|
||||
</OrgPermissionCan>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-mineshaft-300">Allow members to authenticate into Infisical with SAML</p>
|
||||
)}
|
||||
</div>
|
||||
<SSOModal
|
||||
popUp={popUp}
|
||||
handlePopUpClose={handlePopUpClose}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can use SAML SSO if you switch to Infisical's Pro plan."
|
||||
/>
|
||||
<p className="text-sm text-mineshaft-300">
|
||||
Allow members to authenticate into Infisical with SAML
|
||||
</p>
|
||||
</div>
|
||||
<SSOModal
|
||||
popUp={popUp}
|
||||
handlePopUpClose={handlePopUpClose}
|
||||
handlePopUpToggle={handlePopUpToggle}
|
||||
/>
|
||||
<UpgradePlanModal
|
||||
isOpen={popUp.upgradePlan.isOpen}
|
||||
onOpenChange={(isOpen) => handlePopUpToggle("upgradePlan", isOpen)}
|
||||
text="You can use SAML SSO if you switch to Infisical's Pro plan."
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
+108
-133
@@ -28,10 +28,7 @@ import {
|
||||
} from "@app/components/v2";
|
||||
import { useOrganization } from "@app/context";
|
||||
import { useToggle } from "@app/hooks";
|
||||
import {
|
||||
useCreateScimToken,
|
||||
useDeleteScimToken,
|
||||
useGetScimTokens} from "@app/hooks/api";
|
||||
import { useCreateScimToken, useDeleteScimToken, useGetScimTokens } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const schema = yup.object({
|
||||
@@ -46,53 +43,47 @@ type Props = {
|
||||
handlePopUpOpen: (
|
||||
popUpName: keyof UsePopUpState<["deleteScimToken"]>,
|
||||
data?: {
|
||||
scimTokenId: string;
|
||||
scimTokenId: string;
|
||||
}
|
||||
) => void;
|
||||
handlePopUpToggle: (
|
||||
popUpName: keyof UsePopUpState<
|
||||
["scimToken", "deleteScimToken"]
|
||||
>,
|
||||
popUpName: keyof UsePopUpState<["scimToken", "deleteScimToken"]>,
|
||||
state?: boolean
|
||||
) => void;
|
||||
};
|
||||
|
||||
export const ScimTokenModal = ({
|
||||
popUp,
|
||||
handlePopUpOpen,
|
||||
handlePopUpToggle
|
||||
}: Props) => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const { t } = useTranslation();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const [token, setToken] = useState("");
|
||||
export const ScimTokenModal = ({ popUp, handlePopUpOpen, handlePopUpToggle }: Props) => {
|
||||
const { currentOrg } = useOrganization();
|
||||
const { t } = useTranslation();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const [token, setToken] = useState("");
|
||||
|
||||
const [isScimUrlCopied, setIsScimUrlCopied] = useToggle(false);
|
||||
const [isScimTokenCopied, setIsScimTokenCopied] = useToggle(false);
|
||||
|
||||
const { data, isLoading } = useGetScimTokens(currentOrg?.id ?? "");
|
||||
const { mutateAsync: createScimTokenMutateAsync } = useCreateScimToken();
|
||||
const { mutateAsync: deleteScimTokenMutateAsync } = useDeleteScimToken();
|
||||
const [isScimUrlCopied, setIsScimUrlCopied] = useToggle(false);
|
||||
const [isScimTokenCopied, setIsScimTokenCopied] = useToggle(false);
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
description: "",
|
||||
ttlDays: "365"
|
||||
}
|
||||
});
|
||||
const { data, isLoading } = useGetScimTokens(currentOrg?.id ?? "");
|
||||
const { mutateAsync: createScimTokenMutateAsync } = useCreateScimToken();
|
||||
const { mutateAsync: deleteScimTokenMutateAsync } = useDeleteScimToken();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset,
|
||||
formState: { isSubmitting }
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema),
|
||||
defaultValues: {
|
||||
description: "",
|
||||
ttlDays: "365"
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout;
|
||||
if (isScimUrlCopied) {
|
||||
timer = setTimeout(() => setIsScimUrlCopied.off(), 2000);
|
||||
}
|
||||
|
||||
|
||||
if (isScimTokenCopied) {
|
||||
timer = setTimeout(() => setIsScimTokenCopied.off(), 2000);
|
||||
}
|
||||
@@ -102,36 +93,36 @@ export const ScimTokenModal = ({
|
||||
|
||||
const onFormSubmit = async ({ description, ttlDays }: FormData) => {
|
||||
try {
|
||||
if (!currentOrg?.id) return;
|
||||
|
||||
const { scimToken } = await createScimTokenMutateAsync({
|
||||
organizationId: currentOrg.id,
|
||||
description,
|
||||
ttlDays: Number(ttlDays)
|
||||
});
|
||||
|
||||
setToken(scimToken);
|
||||
if (!currentOrg?.id) return;
|
||||
|
||||
createNotification({
|
||||
text: "Successfully created SCIM token",
|
||||
type: "success"
|
||||
});
|
||||
const { scimToken } = await createScimTokenMutateAsync({
|
||||
organizationId: currentOrg.id,
|
||||
description,
|
||||
ttlDays: Number(ttlDays)
|
||||
});
|
||||
|
||||
setToken(scimToken);
|
||||
|
||||
createNotification({
|
||||
text: "Successfully created SCIM token",
|
||||
type: "success"
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to create SCIM token",
|
||||
type: "error"
|
||||
});
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to create SCIM token",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const onDeleteScimTokenSubmit = async (scimTokenId: string) => {
|
||||
try {
|
||||
if (!currentOrg?.id) return;
|
||||
|
||||
|
||||
await deleteScimTokenMutateAsync({
|
||||
organizationId: currentOrg.id,
|
||||
scimTokenId
|
||||
organizationId: currentOrg.id,
|
||||
scimTokenId
|
||||
});
|
||||
|
||||
handlePopUpToggle("deleteScimToken", false);
|
||||
@@ -170,8 +161,8 @@ export const ScimTokenModal = ({
|
||||
colorSchema="secondary"
|
||||
className="group relative"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(scimUrl);
|
||||
setIsScimUrlCopied.on();
|
||||
navigator.clipboard.writeText(scimUrl);
|
||||
setIsScimUrlCopied.on();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={isScimUrlCopied ? faCheck : faCopy} />
|
||||
@@ -203,8 +194,8 @@ export const ScimTokenModal = ({
|
||||
colorSchema="secondary"
|
||||
className="group relative"
|
||||
onClick={() => {
|
||||
navigator.clipboard.writeText(token);
|
||||
setIsScimTokenCopied.on();
|
||||
navigator.clipboard.writeText(token);
|
||||
setIsScimTokenCopied.on();
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={isScimTokenCopied ? faCheck : faCopy} />
|
||||
@@ -230,31 +221,27 @@ export const ScimTokenModal = ({
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue="365"
|
||||
name="ttlDays"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="TTL (days)"
|
||||
isError={Boolean(error)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<div className="flex">
|
||||
<Input {...field} placeholder="0" type="number" min="0" step="1" />
|
||||
<Button
|
||||
className="ml-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
defaultValue="365"
|
||||
name="ttlDays"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl label="TTL (days)" isError={Boolean(error)} errorText={error?.message}>
|
||||
<div className="flex">
|
||||
<Input {...field} placeholder="0" type="number" min="0" step="1" />
|
||||
<Button
|
||||
className="ml-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isSubmitting}
|
||||
isDisabled={isSubmitting}
|
||||
>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
)}
|
||||
<h2 className="mb-4">SCIM Tokens</h2>
|
||||
@@ -273,53 +260,42 @@ export const ScimTokenModal = ({
|
||||
{!isLoading &&
|
||||
data &&
|
||||
data.length > 0 &&
|
||||
data.map(
|
||||
({
|
||||
id,
|
||||
description,
|
||||
ttlDays,
|
||||
createdAt
|
||||
}) => {
|
||||
|
||||
let expiresAt;
|
||||
if (ttlDays > 0) {
|
||||
expiresAt = new Date(new Date(createdAt).getTime() + ttlDays * 86400 * 1000);
|
||||
}
|
||||
|
||||
return (
|
||||
<Tr className="h-10 items-center" key={`mi-client-secret-${id}`}>
|
||||
<Td>{description === "" ? "-" : description}</Td>
|
||||
<Td>{expiresAt ? format(expiresAt, "yyyy-MM-dd HH:mm:ss") : "-"}</Td>
|
||||
<Td>{format(new Date(createdAt), "yyyy-MM-dd HH:mm:ss")}</Td>
|
||||
<Td>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
handlePopUpOpen("deleteScimToken", {
|
||||
scimTokenId: id
|
||||
});
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="primary"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
data.map(({ id, description, ttlDays, createdAt }) => {
|
||||
let expiresAt;
|
||||
if (ttlDays > 0) {
|
||||
expiresAt = new Date(new Date(createdAt).getTime() + ttlDays * 86400 * 1000);
|
||||
}
|
||||
)}
|
||||
{!isLoading && data && data?.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={4}>
|
||||
<EmptyState
|
||||
title="No SCIM tokens have been created yet"
|
||||
icon={faKey}
|
||||
/>
|
||||
</Td>
|
||||
|
||||
return (
|
||||
<Tr className="h-10 items-center" key={`mi-client-secret-${id}`}>
|
||||
<Td>{description === "" ? "-" : description}</Td>
|
||||
<Td>{expiresAt ? format(expiresAt, "yyyy-MM-dd HH:mm:ss") : "-"}</Td>
|
||||
<Td>{format(new Date(createdAt), "yyyy-MM-dd HH:mm:ss")}</Td>
|
||||
<Td>
|
||||
<IconButton
|
||||
onClick={() => {
|
||||
handlePopUpOpen("deleteScimToken", {
|
||||
scimTokenId: id
|
||||
});
|
||||
}}
|
||||
size="lg"
|
||||
colorSchema="primary"
|
||||
variant="plain"
|
||||
ariaLabel="update"
|
||||
>
|
||||
<FontAwesomeIcon icon={faXmark} />
|
||||
</IconButton>
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
);
|
||||
})}
|
||||
{!isLoading && data && data?.length === 0 && (
|
||||
<Tr>
|
||||
<Td colSpan={4}>
|
||||
<EmptyState title="No SCIM tokens have been created yet" icon={faKey} />
|
||||
</Td>
|
||||
</Tr>
|
||||
)}
|
||||
</TBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
@@ -329,9 +305,8 @@ export const ScimTokenModal = ({
|
||||
onChange={(isOpen) => handlePopUpToggle("scimToken", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={() => {
|
||||
|
||||
const deleteScimTokenData = popUp?.deleteScimToken?.data as {
|
||||
scimTokenId: string;
|
||||
scimTokenId: string;
|
||||
};
|
||||
|
||||
return onDeleteScimTokenSubmit(deleteScimTokenData.scimTokenId);
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { OrgAuthTab } from "./OrgAuthTab";
|
||||
export { OrgAuthTab } from "./OrgAuthTab";
|
||||
|
||||
+1
-1
@@ -48,7 +48,7 @@ export const OrgDeleteSection = () => {
|
||||
<>
|
||||
<hr className="border-mineshaft-600" />
|
||||
<div className="py-4">
|
||||
<p className="mb-4 text-md text-mineshaft-100">Danger Zone</p>
|
||||
<p className="text-md mb-4 text-mineshaft-100">Danger Zone</p>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
colorSchema="danger"
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { OrgDeleteSection } from "./OrgDeleteSection";
|
||||
export { OrgDeleteSection } from "./OrgDeleteSection";
|
||||
|
||||
+1
-1
@@ -13,4 +13,4 @@ export const OrgGeneralTab = () => {
|
||||
{membership && membership.role === "admin" && <OrgDeleteSection />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { OrgGeneralTab } from "./OrgGeneralTab";
|
||||
export { OrgGeneralTab } from "./OrgGeneralTab";
|
||||
|
||||
+2
-2
@@ -1,2 +1,2 @@
|
||||
export { AddOrgIncidentContactModal } from "./AddOrgIncidentContactModal"
|
||||
export { OrgIncidentContactsSection } from "./OrgIncidentContactsSection";
|
||||
export { AddOrgIncidentContactModal } from "./AddOrgIncidentContactModal";
|
||||
export { OrgIncidentContactsSection } from "./OrgIncidentContactsSection";
|
||||
|
||||
+16
-12
@@ -10,12 +10,16 @@ import { OrgPermissionActions, OrgPermissionSubjects, useOrganization } from "@a
|
||||
import { useUpdateOrg } from "@app/hooks/api";
|
||||
|
||||
const formSchema = yup.object({
|
||||
name: yup.string().required().label("Organization Name").max(64, "Too long, maximum length is 64 characters"),
|
||||
name: yup
|
||||
.string()
|
||||
.required()
|
||||
.label("Organization Name")
|
||||
.max(64, "Too long, maximum length is 64 characters"),
|
||||
slug: yup
|
||||
.string()
|
||||
.matches(/^[a-zA-Z0-9-]+$/, "Name must only contain alphanumeric characters or hyphens")
|
||||
.required()
|
||||
.label("Organization Slug")
|
||||
.string()
|
||||
.matches(/^[a-zA-Z0-9-]+$/, "Name must only contain alphanumeric characters or hyphens")
|
||||
.required()
|
||||
.label("Organization Slug")
|
||||
});
|
||||
|
||||
type FormData = yup.InferType<typeof formSchema>;
|
||||
@@ -30,7 +34,7 @@ export const OrgNameChangeSection = (): JSX.Element => {
|
||||
|
||||
useEffect(() => {
|
||||
if (currentOrg) {
|
||||
reset({
|
||||
reset({
|
||||
name: currentOrg.name,
|
||||
slug: currentOrg.slug
|
||||
});
|
||||
@@ -41,12 +45,12 @@ export const OrgNameChangeSection = (): JSX.Element => {
|
||||
try {
|
||||
if (!currentOrg?.id) return;
|
||||
|
||||
await mutateAsync({
|
||||
orgId: currentOrg?.id,
|
||||
await mutateAsync({
|
||||
orgId: currentOrg?.id,
|
||||
name,
|
||||
slug
|
||||
});
|
||||
|
||||
|
||||
createNotification({
|
||||
text: "Successfully updated organization details",
|
||||
type: "success"
|
||||
@@ -63,7 +67,7 @@ export const OrgNameChangeSection = (): JSX.Element => {
|
||||
return (
|
||||
<form onSubmit={handleSubmit(onFormSubmit)} className="py-4">
|
||||
<div className="">
|
||||
<h2 className="mb-2 text-md text-mineshaft-100">Organization Name</h2>
|
||||
<h2 className="text-md mb-2 text-mineshaft-100">Organization Name</h2>
|
||||
<Controller
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
@@ -76,7 +80,7 @@ export const OrgNameChangeSection = (): JSX.Element => {
|
||||
/>
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<h2 className="mb-2 text-md text-mineshaft-100">Organization Slug</h2>
|
||||
<h2 className="text-md mb-2 text-mineshaft-100">Organization Slug</h2>
|
||||
<Controller
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
@@ -103,4 +107,4 @@ export const OrgNameChangeSection = (): JSX.Element => {
|
||||
</OrgPermissionCan>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,13 +11,13 @@ const tabs = [
|
||||
export const OrgTabGroup = () => {
|
||||
return (
|
||||
<Tab.Group>
|
||||
<Tab.List className="mb-6 border-b-2 border-mineshaft-800 w-full">
|
||||
<Tab.List className="mb-6 w-full 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 ${
|
||||
className={`w-30 mx-2 mr-4 py-2 text-sm font-medium outline-none ${
|
||||
selected ? "border-b border-white text-white" : "text-mineshaft-400"
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { OrgTabGroup } from "./OrgTabGroup";
|
||||
export { OrgTabGroup } from "./OrgTabGroup";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { OrgTabGroup } from "./OrgTabGroup";
|
||||
export { OrgTabGroup } from "./OrgTabGroup";
|
||||
|
||||
+140
-156
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import { faCheck, faCopy } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { yupResolver } from "@hookform/resolvers/yup";
|
||||
@@ -21,25 +21,27 @@ import { useCreateAPIKey } from "@app/hooks/api";
|
||||
import { UsePopUpState } from "@app/hooks/usePopUp";
|
||||
|
||||
const expirations = [
|
||||
{ label: "1 day", value: "1d" },
|
||||
{ label: "7 days", value: "7d" },
|
||||
{ label: "1 month", value: "1mo" },
|
||||
{ label: "6 months", value: "6mo" },
|
||||
{ label: "12 months", value: "12mo" }
|
||||
{ label: "1 day", value: "1d" },
|
||||
{ label: "7 days", value: "7d" },
|
||||
{ label: "1 month", value: "1mo" },
|
||||
{ label: "6 months", value: "6mo" },
|
||||
{ label: "12 months", value: "12mo" }
|
||||
];
|
||||
|
||||
const expirationMapping: { [key: string]: number } = {
|
||||
"1d": 86400,
|
||||
"7d": 604800,
|
||||
"1mo": 2592000,
|
||||
"6mo": 15552000,
|
||||
"12mo": 31104000
|
||||
}
|
||||
"1d": 86400,
|
||||
"7d": 604800,
|
||||
"1mo": 2592000,
|
||||
"6mo": 15552000,
|
||||
"12mo": 31104000
|
||||
};
|
||||
|
||||
const schema = yup.object({
|
||||
const schema = yup
|
||||
.object({
|
||||
name: yup.string().required("API Key name is required"),
|
||||
expiresIn: yup.string().required("API Key expiration window is required")
|
||||
}).required();
|
||||
})
|
||||
.required();
|
||||
|
||||
export type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
@@ -48,154 +50,136 @@ type Props = {
|
||||
handlePopUpToggle: (popUpName: keyof UsePopUpState<["addAPIKey"]>, state?: boolean) => void;
|
||||
};
|
||||
|
||||
export const AddAPIKeyModal = ({
|
||||
popUp,
|
||||
handlePopUpToggle
|
||||
}: Props) => {
|
||||
const [newAPIKey, setNewAPIKey] = useState("");
|
||||
const [isAPIKeyCopied, setIsAPIKeyCopied] = useToggle(false);
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { mutateAsync, isLoading } = useCreateAPIKey();
|
||||
|
||||
const {
|
||||
control,
|
||||
handleSubmit,
|
||||
reset
|
||||
} = useForm<FormData>({
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
export const AddAPIKeyModal = ({ popUp, handlePopUpToggle }: Props) => {
|
||||
const [newAPIKey, setNewAPIKey] = useState("");
|
||||
const [isAPIKeyCopied, setIsAPIKeyCopied] = useToggle(false);
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { mutateAsync, isLoading } = useCreateAPIKey();
|
||||
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout;
|
||||
const { control, handleSubmit, reset } = useForm<FormData>({
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
|
||||
if (isAPIKeyCopied) {
|
||||
timer = setTimeout(() => setIsAPIKeyCopied.off(), 2000);
|
||||
}
|
||||
useEffect(() => {
|
||||
let timer: NodeJS.Timeout;
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
if (isAPIKeyCopied) {
|
||||
timer = setTimeout(() => setIsAPIKeyCopied.off(), 2000);
|
||||
}
|
||||
|
||||
return () => clearTimeout(timer);
|
||||
}, [setIsAPIKeyCopied]);
|
||||
|
||||
const copyTokenToClipboard = () => {
|
||||
navigator.clipboard.writeText(newAPIKey);
|
||||
setIsAPIKeyCopied.on();
|
||||
};
|
||||
const copyTokenToClipboard = () => {
|
||||
navigator.clipboard.writeText(newAPIKey);
|
||||
setIsAPIKeyCopied.on();
|
||||
};
|
||||
|
||||
const onFormSubmit = async ({ name, expiresIn }: FormData) => {
|
||||
try {
|
||||
const { apiKey } = await mutateAsync({
|
||||
name,
|
||||
expiresIn: expirationMapping[expiresIn]
|
||||
});
|
||||
|
||||
setNewAPIKey(apiKey);
|
||||
const onFormSubmit = async ({ name, expiresIn }: FormData) => {
|
||||
try {
|
||||
const { apiKey } = await mutateAsync({
|
||||
name,
|
||||
expiresIn: expirationMapping[expiresIn]
|
||||
});
|
||||
|
||||
createNotification({
|
||||
text: "Successfully created API key",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
reset();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to create API key",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
setNewAPIKey(apiKey);
|
||||
|
||||
createNotification({
|
||||
text: "Successfully created API key",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
reset();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to create API key",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
const hasAPIKey = Boolean(newAPIKey);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.addAPIKey?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("addAPIKey", isOpen);
|
||||
reset();
|
||||
setNewAPIKey("");
|
||||
}}
|
||||
>
|
||||
<ModalContent title="Create API Key">
|
||||
{!hasAPIKey ? (
|
||||
<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="My API Key"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="expiresIn"
|
||||
defaultValue="6mo"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label="Expiration"
|
||||
errorText={error?.message}
|
||||
isError={Boolean(error)}
|
||||
>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
{expirations.map(({ label, value }) => (
|
||||
<SelectItem value={String(value || "")} key={`api-key-expiration-${label}`}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isLoading}
|
||||
isDisabled={isLoading}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpToggle("addAPIKey", false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<div className="mt-2 mb-3 mr-2 flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||
<p className="mr-4 break-all">{newAPIKey}</p>
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
colorSchema="secondary"
|
||||
className="group relative"
|
||||
onClick={copyTokenToClipboard}
|
||||
};
|
||||
|
||||
const hasAPIKey = Boolean(newAPIKey);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={popUp?.addAPIKey?.isOpen}
|
||||
onOpenChange={(isOpen) => {
|
||||
handlePopUpToggle("addAPIKey", isOpen);
|
||||
reset();
|
||||
setNewAPIKey("");
|
||||
}}
|
||||
>
|
||||
<ModalContent title="Create API Key">
|
||||
{!hasAPIKey ? (
|
||||
<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="My API Key" />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<Controller
|
||||
control={control}
|
||||
name="expiresIn"
|
||||
defaultValue="6mo"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl label="Expiration" errorText={error?.message} isError={Boolean(error)}>
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
{...field}
|
||||
onValueChange={(e) => onChange(e)}
|
||||
className="w-full"
|
||||
>
|
||||
<FontAwesomeIcon icon={isAPIKeyCopied ? faCheck : faCopy} />
|
||||
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
||||
Click to copy
|
||||
</span>
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
{expirations.map(({ label, value }) => (
|
||||
<SelectItem value={String(value || "")} key={`api-key-expiration-${label}`}>
|
||||
{label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="mt-8 flex items-center">
|
||||
<Button
|
||||
className="mr-4"
|
||||
size="sm"
|
||||
type="submit"
|
||||
isLoading={isLoading}
|
||||
isDisabled={isLoading}
|
||||
>
|
||||
Add
|
||||
</Button>
|
||||
<Button
|
||||
colorSchema="secondary"
|
||||
variant="plain"
|
||||
onClick={() => handlePopUpToggle("addAPIKey", false)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
) : (
|
||||
<div className="mt-2 mb-3 mr-2 flex items-center justify-end rounded-md bg-white/[0.07] p-2 text-base text-gray-400">
|
||||
<p className="mr-4 break-all">{newAPIKey}</p>
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
colorSchema="secondary"
|
||||
className="group relative"
|
||||
onClick={copyTokenToClipboard}
|
||||
>
|
||||
<FontAwesomeIcon icon={isAPIKeyCopied ? faCheck : faCopy} />
|
||||
<span className="absolute -left-8 -top-20 hidden w-28 translate-y-full rounded-md bg-bunker-800 py-2 pl-3 text-center text-sm text-gray-400 group-hover:flex group-hover:animate-fadeIn">
|
||||
Click to copy
|
||||
</span>
|
||||
</IconButton>
|
||||
</div>
|
||||
)}
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
|
||||
export { APIKeySection } from "./APIKeySection";
|
||||
export { APIKeySection } from "./APIKeySection";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { AuthMethodSection } from "./AuthMethodSection";
|
||||
export { AuthMethodSection } from "./AuthMethodSection";
|
||||
|
||||
+24
-24
@@ -4,29 +4,29 @@ import { useRouter } from "next/router";
|
||||
import ListBox from "@app/components/basic/Listbox";
|
||||
|
||||
export const ChangeLanguageSection = () => {
|
||||
const { t, i18n } = useTranslation();
|
||||
const router = useRouter();
|
||||
const lang = router.locale ?? "en";
|
||||
const { t, i18n } = useTranslation();
|
||||
const router = useRouter();
|
||||
const lang = router.locale ?? "en";
|
||||
|
||||
const setLanguage = async (to: string) => {
|
||||
router.push(router.asPath, router.asPath, { locale: to });
|
||||
localStorage.setItem("lang", to);
|
||||
i18n.changeLanguage(to);
|
||||
};
|
||||
const setLanguage = async (to: string) => {
|
||||
router.push(router.asPath, router.asPath, { locale: to });
|
||||
localStorage.setItem("lang", to);
|
||||
i18n.changeLanguage(to);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="p-4 bg-mineshaft-900 mb-6 rounded-lg border border-mineshaft-600">
|
||||
<p className="text-xl font-semibold text-mineshaft-100 mb-8">
|
||||
{t("settings.personal.change-language")}
|
||||
</p>
|
||||
<div className="w-28">
|
||||
<ListBox
|
||||
isSelected={lang}
|
||||
onChange={setLanguage}
|
||||
data={["en", "ko", "fr", "es"]}
|
||||
text={`${t("common.language")}: `}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
|
||||
<p className="mb-8 text-xl font-semibold text-mineshaft-100">
|
||||
{t("settings.personal.change-language")}
|
||||
</p>
|
||||
<div className="w-28">
|
||||
<ListBox
|
||||
isSelected={lang}
|
||||
onChange={setLanguage}
|
||||
data={["en", "ko", "fr", "es"]}
|
||||
text={`${t("common.language")}: `}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { ChangeLanguageSection } from "./ChangeLanguageSection";
|
||||
export { ChangeLanguageSection } from "./ChangeLanguageSection";
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { ChangePasswordSection } from "./ChangePasswordSection";
|
||||
export { ChangePasswordSection } from "./ChangePasswordSection";
|
||||
|
||||
+48
-53
@@ -1,64 +1,59 @@
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
import { useNotificationContext } from "@app/components/context/Notifications/NotificationProvider";
|
||||
import {
|
||||
Button,
|
||||
DeleteActionModal
|
||||
} from "@app/components/v2";
|
||||
import { Button, DeleteActionModal } from "@app/components/v2";
|
||||
import { useDeleteUser } from "@app/hooks/api";
|
||||
import { usePopUp } from "@app/hooks/usePopUp";
|
||||
|
||||
export const DeleteAccountSection = () => {
|
||||
const router = useRouter();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"deleteAccount"
|
||||
] as const);
|
||||
const router = useRouter();
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
|
||||
"deleteAccount"
|
||||
] as const);
|
||||
|
||||
const { mutateAsync: deleteUserMutateAsync, isLoading } = useDeleteUser();
|
||||
const { mutateAsync: deleteUserMutateAsync, isLoading } = useDeleteUser();
|
||||
|
||||
const handleDeleteAccountSubmit = async () => {
|
||||
try {
|
||||
await deleteUserMutateAsync();
|
||||
|
||||
createNotification({
|
||||
text: "Successfully deleted account",
|
||||
type: "success"
|
||||
});
|
||||
const handleDeleteAccountSubmit = async () => {
|
||||
try {
|
||||
await deleteUserMutateAsync();
|
||||
|
||||
router.push("/login");
|
||||
handlePopUpClose("deleteAccount");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to delete account",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
createNotification({
|
||||
text: "Successfully deleted account",
|
||||
type: "success"
|
||||
});
|
||||
|
||||
router.push("/login");
|
||||
handlePopUpClose("deleteAccount");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to delete account",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
|
||||
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>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
colorSchema="danger"
|
||||
variant="outline_bg"
|
||||
type="submit"
|
||||
onClick={() => handlePopUpOpen("deleteAccount")}
|
||||
>
|
||||
Delete my account
|
||||
</Button>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteAccount.isOpen}
|
||||
title="Are you sure want to delete your account?"
|
||||
subTitle="Permanently remove this account and all of its data. This action is not reversible, so please be careful."
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteAccount", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={handleDeleteAccountSubmit}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<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"
|
||||
variant="outline_bg"
|
||||
type="submit"
|
||||
onClick={() => handlePopUpOpen("deleteAccount")}
|
||||
>
|
||||
Delete my account
|
||||
</Button>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deleteAccount.isOpen}
|
||||
title="Are you sure want to delete your account?"
|
||||
subTitle="Permanently remove this account and all of its data. This action is not reversible, so please be careful."
|
||||
onChange={(isOpen) => handlePopUpToggle("deleteAccount", isOpen)}
|
||||
deleteKey="confirm"
|
||||
onDeleteApproved={handleDeleteAccountSubmit}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { DeleteAccountSection } from "./DeleteAccountSection";
|
||||
export { DeleteAccountSection } from "./DeleteAccountSection";
|
||||
|
||||
+67
-77
@@ -1,91 +1,81 @@
|
||||
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 issueBackupKey from "@app/components/utilities/cryptography/issueBackupKey";
|
||||
import {
|
||||
Button,
|
||||
FormControl,
|
||||
Input
|
||||
} from "@app/components/v2";
|
||||
import { Button, FormControl, Input } from "@app/components/v2";
|
||||
import { useUser } from "@app/context";
|
||||
|
||||
const schema = yup.object({
|
||||
const schema = yup
|
||||
.object({
|
||||
password: yup.string().required("Password is required")
|
||||
}).required();
|
||||
})
|
||||
.required();
|
||||
|
||||
export type FormData = yup.InferType<typeof schema>;
|
||||
|
||||
export const EmergencyKitSection = () => {
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { user } = useUser();
|
||||
const { reset, control, handleSubmit } = useForm({
|
||||
defaultValues: {
|
||||
password: "",
|
||||
},
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
|
||||
const onFormSubmit = ({
|
||||
password
|
||||
}: FormData) => {
|
||||
try {
|
||||
if (!user?.email) return;
|
||||
const { createNotification } = useNotificationContext();
|
||||
const { user } = useUser();
|
||||
const { reset, control, handleSubmit } = useForm({
|
||||
defaultValues: {
|
||||
password: ""
|
||||
},
|
||||
resolver: yupResolver(schema)
|
||||
});
|
||||
|
||||
issueBackupKey({
|
||||
email: user.email,
|
||||
password,
|
||||
personalName: `${user.firstName} ${user.lastName}`,
|
||||
setBackupKeyError: () => {},
|
||||
setBackupKeyIssued: () => {}
|
||||
});
|
||||
|
||||
reset();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to download emergency kit",
|
||||
type: "error"
|
||||
});
|
||||
}
|
||||
const onFormSubmit = ({ password }: FormData) => {
|
||||
try {
|
||||
if (!user?.email) return;
|
||||
|
||||
issueBackupKey({
|
||||
email: user.email,
|
||||
password,
|
||||
personalName: `${user.firstName} ${user.lastName}`,
|
||||
setBackupKeyError: () => {},
|
||||
setBackupKeyIssued: () => {}
|
||||
});
|
||||
|
||||
reset();
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
createNotification({
|
||||
text: "Failed to download emergency kit",
|
||||
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">
|
||||
Emergency Kit
|
||||
</h2>
|
||||
<p className="text-gray-400 mb-8">
|
||||
The kit contains information you can use to recover your account.
|
||||
</p>
|
||||
<div className="max-w-md">
|
||||
<Controller
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||
<Input
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
{...field}
|
||||
className="bg-mineshaft-800"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="password"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
type="submit"
|
||||
colorSchema="secondary"
|
||||
isLoading={false}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onFormSubmit)}
|
||||
className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||
>
|
||||
<h2 className="flex-1 text-xl font-semibold text-mineshaft-100">Emergency Kit</h2>
|
||||
<p className="mb-8 text-gray-400">
|
||||
The kit contains information you can use to recover your account.
|
||||
</p>
|
||||
<div className="max-w-md">
|
||||
<Controller
|
||||
defaultValue=""
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl isError={Boolean(error)} errorText={error?.message}>
|
||||
<Input
|
||||
placeholder="Password"
|
||||
type="password"
|
||||
{...field}
|
||||
className="bg-mineshaft-800"
|
||||
/>
|
||||
</FormControl>
|
||||
)}
|
||||
control={control}
|
||||
name="password"
|
||||
/>
|
||||
</div>
|
||||
<Button type="submit" colorSchema="secondary" isLoading={false}>
|
||||
Save
|
||||
</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1 +1 @@
|
||||
export { EmergencyKitSection } from "./EmergencyKitSection";
|
||||
export { EmergencyKitSection } from "./EmergencyKitSection";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user