mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-21 16:28:12 +00:00
feat: renamed old frontend and fixed some minor bugs
This commit is contained in:
+259
@@ -0,0 +1,259 @@
|
||||
import { faEllipsisV, faFolder, faPlus, faTrash } from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { format, formatDistance } from "date-fns";
|
||||
import { AnimatePresence, motion } from "framer-motion";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import { ProjectPermissionCan } from "@app/components/permissions";
|
||||
import {
|
||||
DeleteActionModal,
|
||||
EmptyState,
|
||||
IconButton,
|
||||
Table,
|
||||
TableContainer,
|
||||
TableSkeleton,
|
||||
Tag,
|
||||
TBody,
|
||||
Td,
|
||||
Th,
|
||||
THead,
|
||||
Tooltip,
|
||||
Tr
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
useProjectPermission,
|
||||
useUser
|
||||
} from "@app/context";
|
||||
import { usePopUp } from "@app/hooks";
|
||||
import {
|
||||
useDeleteProjectUserAdditionalPrivilege,
|
||||
useListProjectUserPrivileges
|
||||
} from "@app/hooks/api";
|
||||
import { TWorkspaceUser } from "@app/hooks/api/types";
|
||||
|
||||
import { MembershipProjectAdditionalPrivilegeModifySection } from "./MembershipProjectAdditionalPrivilegeModifySection";
|
||||
|
||||
type Props = {
|
||||
membershipDetails: TWorkspaceUser;
|
||||
};
|
||||
|
||||
export const MemberProjectAdditionalPrivilegeSection = ({ membershipDetails }: Props) => {
|
||||
const { user } = useUser();
|
||||
const userId = user?.id;
|
||||
const { popUp, handlePopUpOpen, handlePopUpToggle, handlePopUpClose } = usePopUp([
|
||||
"deletePrivilege",
|
||||
"modifyPrivilege"
|
||||
] as const);
|
||||
const { permission } = useProjectPermission();
|
||||
|
||||
const { mutateAsync: deletePrivilege } = useDeleteProjectUserAdditionalPrivilege();
|
||||
|
||||
const { data: userProjectPrivileges, isPending } = useListProjectUserPrivileges(
|
||||
membershipDetails?.id
|
||||
);
|
||||
|
||||
const isOwnProjectMembershipDetails = userId === membershipDetails?.user?.id;
|
||||
|
||||
const handlePrivilegeDelete = async () => {
|
||||
const { id } = popUp?.deletePrivilege?.data as { id: string };
|
||||
try {
|
||||
await deletePrivilege({
|
||||
privilegeId: id,
|
||||
projectMembershipId: membershipDetails.id
|
||||
});
|
||||
createNotification({ type: "success", text: "Successfully removed the privilege" });
|
||||
handlePopUpClose("deletePrivilege");
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
createNotification({ type: "error", text: "Failed to delete privilege" });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<AnimatePresence>
|
||||
{popUp?.modifyPrivilege.isOpen ? (
|
||||
<motion.div
|
||||
key="privilege-modify"
|
||||
transition={{ duration: 0.3 }}
|
||||
initial={{ opacity: 0, translateX: 30 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: 30 }}
|
||||
className="absolute min-h-[10rem] w-full"
|
||||
>
|
||||
<MembershipProjectAdditionalPrivilegeModifySection
|
||||
onGoBack={() => handlePopUpClose("modifyPrivilege")}
|
||||
projectMembershipId={membershipDetails?.id}
|
||||
privilegeId={(popUp?.modifyPrivilege?.data as { id: string })?.id}
|
||||
isDisabled={
|
||||
isOwnProjectMembershipDetails ||
|
||||
permission.cannot(ProjectPermissionActions.Edit, ProjectPermissionSub.Member)
|
||||
}
|
||||
/>
|
||||
</motion.div>
|
||||
) : (
|
||||
<motion.div
|
||||
key="privilege-list"
|
||||
transition={{ duration: 0.3 }}
|
||||
initial={{ opacity: 0, translateX: 0 }}
|
||||
animate={{ opacity: 1, translateX: 0 }}
|
||||
exit={{ opacity: 0, translateX: -30 }}
|
||||
className="absolute w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||
>
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
|
||||
<h3 className="text-lg font-semibold text-mineshaft-100">
|
||||
Project Additional Privileges
|
||||
</h3>
|
||||
{userId !== membershipDetails?.user?.id &&
|
||||
membershipDetails?.status !== "invited" && (
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={ProjectPermissionSub.Member}
|
||||
renderTooltip
|
||||
allowedLabel="Add Privilege"
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
ariaLabel="copy icon"
|
||||
variant="plain"
|
||||
className="group relative"
|
||||
onClick={() => {
|
||||
handlePopUpOpen("modifyPrivilege");
|
||||
}}
|
||||
isDisabled={!isAllowed}
|
||||
>
|
||||
<FontAwesomeIcon icon={faPlus} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
)}
|
||||
</div>
|
||||
<div className="py-4">
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<THead>
|
||||
<Tr>
|
||||
<Th>Name</Th>
|
||||
<Th>Duration</Th>
|
||||
<Th className="w-5" />
|
||||
</Tr>
|
||||
</THead>
|
||||
<TBody>
|
||||
{isPending && <TableSkeleton columns={3} innerKey="user-project-memberships" />}
|
||||
{!isPending &&
|
||||
userProjectPrivileges?.map((privilegeDetails) => {
|
||||
const isTemporary = privilegeDetails?.isTemporary;
|
||||
const isExpired =
|
||||
privilegeDetails.isTemporary &&
|
||||
new Date() > new Date(privilegeDetails.temporaryAccessEndTime || "");
|
||||
|
||||
let text = "Permanent";
|
||||
let toolTipText = "Non-Expiring Access";
|
||||
if (privilegeDetails.isTemporary) {
|
||||
if (isExpired) {
|
||||
text = "Access Expired";
|
||||
toolTipText = "Timed Access Expired";
|
||||
} else {
|
||||
text = formatDistance(
|
||||
new Date(privilegeDetails.temporaryAccessEndTime || ""),
|
||||
new Date()
|
||||
);
|
||||
toolTipText = `Until ${format(
|
||||
new Date(privilegeDetails.temporaryAccessEndTime || ""),
|
||||
"yyyy-MM-dd hh:mm:ss aaa"
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Tr
|
||||
key={`user-project-privilege-${privilegeDetails?.id}`}
|
||||
className="group w-full cursor-pointer transition-colors duration-100 hover:bg-mineshaft-700"
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
onKeyDown={(evt) => {
|
||||
if (evt.key === "Enter") {
|
||||
handlePopUpOpen("modifyPrivilege", privilegeDetails);
|
||||
}
|
||||
}}
|
||||
onClick={() => handlePopUpOpen("modifyPrivilege", privilegeDetails)}
|
||||
>
|
||||
<Td>{privilegeDetails.slug}</Td>
|
||||
<Td>
|
||||
<Tooltip asChild={false} content={toolTipText}>
|
||||
<Tag
|
||||
className={twMerge(
|
||||
"capitalize",
|
||||
isTemporary && "text-primary",
|
||||
isExpired && "text-red-600"
|
||||
)}
|
||||
>
|
||||
{text}
|
||||
</Tag>
|
||||
</Tooltip>
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex space-x-2 opacity-0 transition-opacity duration-300 group-hover:opacity-100">
|
||||
<ProjectPermissionCan
|
||||
I={ProjectPermissionActions.Edit}
|
||||
a={ProjectPermissionSub.Member}
|
||||
renderTooltip
|
||||
allowedLabel="Remove Role"
|
||||
>
|
||||
{(isAllowed) => (
|
||||
<IconButton
|
||||
colorSchema="danger"
|
||||
ariaLabel="delete-icon"
|
||||
variant="plain"
|
||||
className="group relative"
|
||||
isDisabled={!isAllowed || isOwnProjectMembershipDetails}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
handlePopUpOpen("deletePrivilege", {
|
||||
id: privilegeDetails?.id,
|
||||
slug: privilegeDetails?.slug
|
||||
});
|
||||
}}
|
||||
>
|
||||
<FontAwesomeIcon icon={faTrash} />
|
||||
</IconButton>
|
||||
)}
|
||||
</ProjectPermissionCan>
|
||||
<IconButton
|
||||
ariaLabel="more-icon"
|
||||
variant="plain"
|
||||
className="group relative"
|
||||
>
|
||||
<FontAwesomeIcon icon={faEllipsisV} />
|
||||
</IconButton>
|
||||
</div>
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</TBody>
|
||||
</Table>
|
||||
{!isPending && !userProjectPrivileges?.length && (
|
||||
<EmptyState title="This user has no additional privileges" icon={faFolder} />
|
||||
)}
|
||||
</TableContainer>
|
||||
</div>
|
||||
<DeleteActionModal
|
||||
isOpen={popUp.deletePrivilege.isOpen}
|
||||
deleteKey="remove"
|
||||
title={`Do you want to remove role ${
|
||||
(popUp?.deletePrivilege?.data as { slug: string; id: string })?.slug
|
||||
}?`}
|
||||
onChange={(isOpen) => handlePopUpToggle("deletePrivilege", isOpen)}
|
||||
onDeleteApproved={() => handlePrivilegeDelete()}
|
||||
/>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
+429
@@ -0,0 +1,429 @@
|
||||
import { Controller, FormProvider, useForm } from "react-hook-form";
|
||||
import {
|
||||
faCaretDown,
|
||||
faChevronLeft,
|
||||
faClock,
|
||||
faPlus,
|
||||
faSave
|
||||
} from "@fortawesome/free-solid-svg-icons";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { format, formatDistance } from "date-fns";
|
||||
import ms from "ms";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import { z } from "zod";
|
||||
|
||||
import { TtlFormLabel } from "@app/components/features";
|
||||
import { createNotification } from "@app/components/notifications";
|
||||
import {
|
||||
Button,
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
FormControl,
|
||||
FormLabel,
|
||||
Input,
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Tag,
|
||||
Tooltip
|
||||
} from "@app/components/v2";
|
||||
import {
|
||||
ProjectPermissionActions,
|
||||
ProjectPermissionSub,
|
||||
useProjectPermission,
|
||||
useWorkspace
|
||||
} from "@app/context";
|
||||
import {
|
||||
useCreateProjectUserAdditionalPrivilege,
|
||||
useGetProjectUserPrivilegeDetails,
|
||||
useUpdateProjectUserAdditionalPrivilege
|
||||
} from "@app/hooks/api";
|
||||
import { ProjectUserAdditionalPrivilegeTemporaryMode } from "@app/hooks/api/projectUserAdditionalPrivilege/types";
|
||||
import { GeneralPermissionPolicies } from "@app/pages/project/RoleDetailsBySlugPage/components/GeneralPermissionPolicies";
|
||||
import { PermissionEmptyState } from "@app/pages/project/RoleDetailsBySlugPage/components/PermissionEmptyState";
|
||||
import {
|
||||
formRolePermission2API,
|
||||
isConditionalSubjects,
|
||||
PROJECT_PERMISSION_OBJECT,
|
||||
projectRoleFormSchema,
|
||||
rolePermission2Form
|
||||
} from "@app/pages/project/RoleDetailsBySlugPage/components/ProjectRoleModifySection.utils";
|
||||
import { renderConditionalComponents } from "@app/pages/project/RoleDetailsBySlugPage/components/RolePermissionsSection";
|
||||
|
||||
type Props = {
|
||||
privilegeId?: string;
|
||||
projectMembershipId: string;
|
||||
onGoBack: () => void;
|
||||
isDisabled?: boolean;
|
||||
};
|
||||
|
||||
export const formSchema = z.object({
|
||||
slug: z.string().optional(),
|
||||
temporaryAccess: z
|
||||
.discriminatedUnion("isTemporary", [
|
||||
z.object({
|
||||
isTemporary: z.literal(true),
|
||||
temporaryRange: z.string().min(1),
|
||||
temporaryAccessStartTime: z.string().datetime(),
|
||||
temporaryAccessEndTime: z.string().datetime().nullable().optional()
|
||||
}),
|
||||
z.object({
|
||||
isTemporary: z.literal(false)
|
||||
})
|
||||
])
|
||||
.default({ isTemporary: false }),
|
||||
permissions: projectRoleFormSchema.shape.permissions
|
||||
});
|
||||
|
||||
type TFormSchema = z.infer<typeof formSchema>;
|
||||
|
||||
export const MembershipProjectAdditionalPrivilegeModifySection = ({
|
||||
privilegeId,
|
||||
onGoBack,
|
||||
projectMembershipId,
|
||||
isDisabled
|
||||
}: Props) => {
|
||||
const isCreate = !privilegeId;
|
||||
const { currentWorkspace } = useWorkspace();
|
||||
const projectId = currentWorkspace?.id || "";
|
||||
const { data: privilegeDetails, isPending } = useGetProjectUserPrivilegeDetails(
|
||||
privilegeId || ""
|
||||
);
|
||||
|
||||
const { permission } = useProjectPermission();
|
||||
const isMemberEditDisabled = permission.cannot(
|
||||
ProjectPermissionActions.Edit,
|
||||
ProjectPermissionSub.Member
|
||||
);
|
||||
|
||||
const form = useForm<TFormSchema>({
|
||||
values: privilegeDetails
|
||||
? {
|
||||
...privilegeDetails,
|
||||
permissions: rolePermission2Form(privilegeDetails.permissions),
|
||||
temporaryAccess: privilegeDetails.isTemporary
|
||||
? {
|
||||
isTemporary: true,
|
||||
temporaryRange: privilegeDetails.temporaryRange || "",
|
||||
temporaryAccessEndTime: privilegeDetails.temporaryAccessEndTime || "",
|
||||
temporaryAccessStartTime: privilegeDetails.temporaryAccessStartTime || ""
|
||||
}
|
||||
: {
|
||||
isTemporary: privilegeDetails.isTemporary
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
resolver: zodResolver(formSchema)
|
||||
});
|
||||
|
||||
const {
|
||||
handleSubmit,
|
||||
formState: { isDirty, isSubmitting }
|
||||
} = form;
|
||||
|
||||
const { mutateAsync: updateUserProjectAdditionalPrivilege } =
|
||||
useUpdateProjectUserAdditionalPrivilege();
|
||||
const { mutateAsync: createUserProjectAdditionalPrivilege } =
|
||||
useCreateProjectUserAdditionalPrivilege();
|
||||
|
||||
const onSubmit = async (el: TFormSchema) => {
|
||||
const accessType = !el.temporaryAccess.isTemporary
|
||||
? { isTemporary: false as const }
|
||||
: {
|
||||
isTemporary: true as const,
|
||||
temporaryMode: ProjectUserAdditionalPrivilegeTemporaryMode.Relative,
|
||||
temporaryRange: el.temporaryAccess.temporaryRange,
|
||||
temporaryAccessStartTime: el.temporaryAccess.temporaryAccessStartTime
|
||||
};
|
||||
|
||||
try {
|
||||
if (isCreate) {
|
||||
await createUserProjectAdditionalPrivilege({
|
||||
permissions: formRolePermission2API(el.permissions),
|
||||
projectMembershipId,
|
||||
slug: el.slug || undefined,
|
||||
type: accessType
|
||||
});
|
||||
createNotification({ type: "success", text: "Successfully created privilege" });
|
||||
} else {
|
||||
if (!projectId || !privilegeDetails?.id) return;
|
||||
await updateUserProjectAdditionalPrivilege({
|
||||
privilegeId: privilegeDetails.id,
|
||||
permissions: formRolePermission2API(el.permissions),
|
||||
projectMembershipId,
|
||||
slug: el.slug || undefined,
|
||||
type: accessType
|
||||
});
|
||||
createNotification({ type: "success", text: "Successfully updated privilege" });
|
||||
}
|
||||
onGoBack();
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
createNotification({ type: "error", text: "Failed to update privilege" });
|
||||
}
|
||||
};
|
||||
|
||||
const onNewPolicy = (selectedSubject: ProjectPermissionSub) => {
|
||||
const rootPolicyValue = form.getValues(`permissions.${selectedSubject}`);
|
||||
if (rootPolicyValue && isConditionalSubjects(selectedSubject)) {
|
||||
form.setValue(
|
||||
`permissions.${selectedSubject}`,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
|
||||
[...rootPolicyValue, ...[]],
|
||||
{ shouldDirty: true, shouldTouch: true }
|
||||
);
|
||||
} else {
|
||||
form.setValue(
|
||||
`permissions.${selectedSubject}`,
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore-error akhilmhdh: this is because of ts collision with both
|
||||
[{}],
|
||||
{
|
||||
shouldDirty: true,
|
||||
shouldTouch: true
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const privilegeTemporaryAccess = form.watch("temporaryAccess");
|
||||
const isTemporary = privilegeTemporaryAccess?.isTemporary;
|
||||
const isExpired =
|
||||
privilegeTemporaryAccess?.isTemporary &&
|
||||
new Date() > new Date(privilegeTemporaryAccess.temporaryAccessEndTime || "");
|
||||
let text = "Permanent";
|
||||
let toolTipText = "Non-Expiring Access";
|
||||
|
||||
if (isTemporary) {
|
||||
if (isExpired) {
|
||||
text = "Access Expired";
|
||||
toolTipText = "Timed Access Expired";
|
||||
} else {
|
||||
text = formatDistance(
|
||||
new Date(privilegeTemporaryAccess.temporaryAccessEndTime || ""),
|
||||
new Date()
|
||||
);
|
||||
toolTipText = `Until ${format(
|
||||
new Date(privilegeTemporaryAccess.temporaryAccessEndTime || ""),
|
||||
"yyyy-MM-dd hh:mm:ss aaa"
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit(onSubmit)}
|
||||
className="w-full rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4"
|
||||
>
|
||||
<FormProvider {...form}>
|
||||
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-2">
|
||||
<Button
|
||||
leftIcon={<FontAwesomeIcon icon={faChevronLeft} />}
|
||||
className="text-lg font-semibold text-mineshaft-100"
|
||||
variant="link"
|
||||
onClick={onGoBack}
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<div className="flex items-center space-x-4">
|
||||
{isDirty && (
|
||||
<Button
|
||||
className="mr-4 text-mineshaft-300"
|
||||
variant="link"
|
||||
isDisabled={isSubmitting}
|
||||
isLoading={isSubmitting}
|
||||
onClick={onGoBack}
|
||||
>
|
||||
Discard
|
||||
</Button>
|
||||
)}
|
||||
<div className="flex items-center">
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
type="submit"
|
||||
className={twMerge("h-10 rounded-r-none", isDirty && "bg-primary text-black")}
|
||||
isDisabled={isSubmitting || !isDirty || isDisabled}
|
||||
isLoading={isSubmitting}
|
||||
leftIcon={<FontAwesomeIcon icon={faSave} />}
|
||||
>
|
||||
Save
|
||||
</Button>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger>
|
||||
<Button
|
||||
isDisabled={isDisabled}
|
||||
className="h-10 rounded-l-none"
|
||||
variant="outline_bg"
|
||||
leftIcon={<FontAwesomeIcon icon={faPlus} />}
|
||||
>
|
||||
New policy
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="thin-scrollbar max-h-96" align="end">
|
||||
{Object.keys(PROJECT_PERMISSION_OBJECT)
|
||||
.sort((a, b) =>
|
||||
PROJECT_PERMISSION_OBJECT[a as keyof typeof PROJECT_PERMISSION_OBJECT].title
|
||||
.toLowerCase()
|
||||
.localeCompare(
|
||||
PROJECT_PERMISSION_OBJECT[
|
||||
b as keyof typeof PROJECT_PERMISSION_OBJECT
|
||||
].title.toLowerCase()
|
||||
)
|
||||
)
|
||||
.map((subject) => (
|
||||
<DropdownMenuItem
|
||||
key={`permission-create-${subject}`}
|
||||
className="py-3"
|
||||
onClick={() => onNewPolicy(subject as ProjectPermissionSub)}
|
||||
>
|
||||
{PROJECT_PERMISSION_OBJECT[subject as ProjectPermissionSub].title}
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 border-b border-gray-800 p-4 pt-2 first:rounded-t-md last:rounded-b-md">
|
||||
<div className="text-lg">Overview</div>
|
||||
<p className="mb-4 text-sm text-mineshaft-300">
|
||||
Additional privileges take precedence over roles when permissions conflict
|
||||
</p>
|
||||
<div className="flex items-end space-x-6">
|
||||
<div className="w-full max-w-md">
|
||||
<Controller
|
||||
control={form.control}
|
||||
name="slug"
|
||||
render={({ field }) => (
|
||||
<FormControl label="Privilege Name" isOptional className="mb-0">
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Popover>
|
||||
<PopoverTrigger disabled={isMemberEditDisabled} asChild>
|
||||
<div className="w-full max-w-md flex-grow">
|
||||
<FormLabel label="Duration" />
|
||||
<Tooltip content={toolTipText}>
|
||||
<Button
|
||||
variant="outline_bg"
|
||||
leftIcon={isTemporary ? <FontAwesomeIcon icon={faClock} /> : undefined}
|
||||
rightIcon={<FontAwesomeIcon icon={faCaretDown} className="ml-2" />}
|
||||
isDisabled={isMemberEditDisabled}
|
||||
className={twMerge(
|
||||
"w-full border-none bg-mineshaft-600 py-2.5 text-xs capitalize hover:bg-mineshaft-500",
|
||||
isTemporary && "text-primary",
|
||||
isExpired && "text-red-600"
|
||||
)}
|
||||
>
|
||||
{text}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
arrowClassName="fill-gray-600"
|
||||
side="right"
|
||||
sideOffset={12}
|
||||
hideCloseBtn
|
||||
className="border border-gray-600 pt-4"
|
||||
>
|
||||
<div className="flex flex-col space-y-4">
|
||||
<div className="border-b border-b-gray-700 pb-2 text-sm text-mineshaft-300">
|
||||
Configure Timed Access
|
||||
</div>
|
||||
{isExpired && <Tag colorSchema="red">Expired</Tag>}
|
||||
<Controller
|
||||
control={form.control}
|
||||
defaultValue="1h"
|
||||
name="temporaryAccess.temporaryRange"
|
||||
render={({ field, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
label={<TtlFormLabel label="Validity" />}
|
||||
isError={Boolean(error?.message)}
|
||||
errorText={error?.message}
|
||||
>
|
||||
<Input {...field} />
|
||||
</FormControl>
|
||||
)}
|
||||
/>
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
size="xs"
|
||||
onClick={() => {
|
||||
const temporaryRange = form.getValues("temporaryAccess.temporaryRange");
|
||||
if (!temporaryRange) {
|
||||
form.setError(
|
||||
"temporaryAccess.temporaryRange",
|
||||
{ type: "required", message: "Required" },
|
||||
{ shouldFocus: true }
|
||||
);
|
||||
return;
|
||||
}
|
||||
form.clearErrors("temporaryAccess.temporaryRange");
|
||||
form.setValue(
|
||||
"temporaryAccess",
|
||||
{
|
||||
isTemporary: true,
|
||||
temporaryAccessStartTime: new Date().toISOString(),
|
||||
temporaryRange,
|
||||
temporaryAccessEndTime: new Date(
|
||||
new Date().getTime() + ms(temporaryRange)
|
||||
).toISOString()
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
}}
|
||||
>
|
||||
{isTemporary ? "Restart" : "Grant"}
|
||||
</Button>
|
||||
{isTemporary && (
|
||||
<Button
|
||||
size="xs"
|
||||
variant="outline_bg"
|
||||
colorSchema="danger"
|
||||
onClick={() => {
|
||||
form.setValue(
|
||||
"temporaryAccess",
|
||||
{
|
||||
isTemporary: false
|
||||
},
|
||||
{ shouldDirty: true }
|
||||
);
|
||||
}}
|
||||
>
|
||||
Revoke Access
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4">
|
||||
<div className="mb-2 text-lg">Policies</div>
|
||||
{(isCreate || !isPending) && <PermissionEmptyState />}
|
||||
{(Object.keys(PROJECT_PERMISSION_OBJECT) as ProjectPermissionSub[]).map((subject) => (
|
||||
<GeneralPermissionPolicies
|
||||
subject={subject}
|
||||
actions={PROJECT_PERMISSION_OBJECT[subject].actions}
|
||||
title={PROJECT_PERMISSION_OBJECT[subject].title}
|
||||
key={`project-permission-${subject}`}
|
||||
isDisabled={isDisabled}
|
||||
>
|
||||
{renderConditionalComponents(subject, isDisabled)}
|
||||
</GeneralPermissionPolicies>
|
||||
))}
|
||||
</div>
|
||||
</FormProvider>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
+1
@@ -0,0 +1 @@
|
||||
export { MemberProjectAdditionalPrivilegeSection } from "./MemberProjectAdditionalPrivilegeSection";
|
||||
Reference in New Issue
Block a user