feat: fixed metadata to be single origin and return in all scopes

equally and some ui improvement
This commit is contained in:
=
2025-11-13 12:52:39 +05:30
parent a9c4a682d1
commit 4bb13d592f
11 changed files with 165 additions and 116 deletions
@@ -99,3 +99,18 @@ export const useGetProjectIdentityMembership = (projectId: string, identityId: s
}
});
};
export const useGetProjectIdentityMembershipV2 = (projectId: string, identityId: string) => {
return useQuery({
enabled: Boolean(projectId && identityId),
queryKey: projectKeys.getProjectIdentityMembershipDetailsV2(projectId, identityId),
queryFn: async () => {
const {
data: { identityMembership }
} = await apiRequest.get<{ identityMembership: IdentityProjectMembership }>(
`/api/v2/projects/${projectId}/identity-memberships/${identityId}`
);
return identityMembership;
}
});
};
@@ -23,6 +23,8 @@ export const projectKeys = {
[{ projectId }, "project-identity-memberships"] as const,
getProjectIdentityMembershipDetails: (projectId: string, identityId: string) =>
[{ projectId, identityId }, "project-identity-membership-details"] as const,
getProjectIdentityMembershipDetailsV2: (projectId: string, identityId: string) =>
[{ projectId, identityId }, "project-identity-membership-details"] as const,
// allows invalidation using above key without knowing params
getProjectIdentityMembershipsWithParams: ({ projectId, ...params }: TListProjectIdentitiesDTO) =>
[...projectKeys.getProjectIdentityMemberships(projectId), params] as const,
@@ -242,67 +242,75 @@ export const OrgIdentityModal = ({ popUp, handlePopUpToggle }: Props) => {
)}
/>
)}
<div>
<FormLabel label="Metadata" />
</div>
<div className="mb-3 flex flex-col space-y-2">
{metadataFormFields.fields.map(({ id: metadataFieldId }, i) => (
<div key={metadataFieldId} className="flex items-end space-x-2">
<div className="grow">
{i === 0 && <span className="text-xs text-mineshaft-400">Key</span>}
<Controller
control={control}
name={`metadata.${i}.key`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0"
>
<Input {...field} />
</FormControl>
)}
/>
</div>
<div className="grow">
{i === 0 && (
<FormLabel label="Value" className="text-xs text-mineshaft-400" isOptional />
)}
<Controller
control={control}
name={`metadata.${i}.value`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0"
>
<Input {...field} />
</FormControl>
)}
/>
</div>
<IconButton
ariaLabel="delete key"
className="bottom-0.5 h-9"
variant="outline_bg"
onClick={() => metadataFormFields.remove(i)}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
{isOrgIdentity && (
<>
<div>
<FormLabel label="Metadata" />
</div>
))}
<div className="mt-2 flex justify-end">
<Button
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
variant="outline_bg"
onClick={() => metadataFormFields.append({ key: "", value: "" })}
>
Add Key
</Button>
</div>
</div>
<div className="mb-3 flex flex-col space-y-2">
{metadataFormFields.fields.map(({ id: metadataFieldId }, i) => (
<div key={metadataFieldId} className="flex items-end space-x-2">
<div className="grow">
{i === 0 && <span className="text-xs text-mineshaft-400">Key</span>}
<Controller
control={control}
name={`metadata.${i}.key`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0"
>
<Input {...field} />
</FormControl>
)}
/>
</div>
<div className="grow">
{i === 0 && (
<FormLabel
label="Value"
className="text-xs text-mineshaft-400"
isOptional
/>
)}
<Controller
control={control}
name={`metadata.${i}.value`}
render={({ field, fieldState: { error } }) => (
<FormControl
isError={Boolean(error?.message)}
errorText={error?.message}
className="mb-0"
>
<Input {...field} />
</FormControl>
)}
/>
</div>
<IconButton
ariaLabel="delete key"
className="bottom-0.5 h-9"
variant="outline_bg"
onClick={() => metadataFormFields.remove(i)}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</div>
))}
<div className="mt-2 flex justify-end">
<Button
leftIcon={<FontAwesomeIcon icon={faPlus} />}
size="xs"
variant="outline_bg"
onClick={() => metadataFormFields.append({ key: "", value: "" })}
>
Add Key
</Button>
</div>
</div>
</>
)}
<div className="flex items-center">
<Button
className="mr-4"
@@ -28,7 +28,8 @@ import { usePopUp } from "@app/hooks";
import {
useAssumeProjectPrivileges,
useDeleteProjectIdentityMembership,
useGetProjectIdentityMembership
useGetProjectIdentityMembership,
useGetProjectIdentityMembershipV2
} from "@app/hooks/api";
import { ActorType } from "@app/hooks/api/auditLogs/enums";
import { projectIdentityQuery } from "@app/hooks/api/projectIdentity";
@@ -48,7 +49,7 @@ const Page = () => {
const { currentProject, projectId } = useProject();
const { data: identityMembershipDetails, isPending: isMembershipDetailsLoading } =
useGetProjectIdentityMembership(projectId, identityId);
useGetProjectIdentityMembershipV2(projectId, identityId);
const { mutateAsync: deleteMutateAsync, isPending: isDeletingIdentity } =
useDeleteProjectIdentityMembership();
@@ -204,7 +205,7 @@ const Page = () => {
</div>
</PageHeader>
<div className="flex gap-x-4">
{identity && (
{identity ? (
<div className="flex w-72 flex-col gap-y-4">
<ProjectIdentityDetailsSection
identity={identity}
@@ -215,6 +216,16 @@ const Page = () => {
refetchIdentity={() => refetchIdentity()}
/>
</div>
) : (
<div>
<div className="flex w-72 flex-col gap-y-4">
<ProjectIdentityDetailsSection
identity={{ ...identityMembershipDetails?.identity, projectId: "" }}
isOrgIdentity
membership={identityMembershipDetails!}
/>
</div>
</div>
)}
<div className="flex-1">
<IdentityRoleDetailsSection
@@ -26,6 +26,7 @@ import {
ProjectPermissionActions,
ProjectPermissionIdentityActions,
ProjectPermissionSub,
useProject,
useProjectPermission
} from "@app/context";
import { usePopUp } from "@app/hooks";
@@ -46,13 +47,13 @@ export const IdentityProjectAdditionalPrivilegeSection = ({ identityMembershipDe
] as const);
const { permission } = useProjectPermission();
const identityId = identityMembershipDetails?.identity?.id;
const projectId = identityMembershipDetails?.project?.id;
const { projectId } = useProject();
const { mutateAsync: deletePrivilege } = useDeleteIdentityProjectAdditionalPrivilege();
const { data: identityProjectPrivileges, isPending } = useListIdentityProjectPrivileges({
identityId: identityMembershipDetails?.identity?.id,
projectId: identityMembershipDetails?.project?.id
projectId
});
const handlePrivilegeDelete = async () => {
@@ -34,10 +34,11 @@ import { ProjectIdentityModal } from "@app/pages/project/AccessControlPage/compo
type Props = {
identity: TProjectIdentity;
isOrgIdentity?: boolean;
membership: IdentityProjectMembership;
};
export const ProjectIdentityDetailsSection = ({ identity, membership }: Props) => {
export const ProjectIdentityDetailsSection = ({ identity, isOrgIdentity, membership }: Props) => {
const [copyTextId, isCopyingId, setCopyTextId] = useTimedReset<string>({
initialState: "Copy ID to clipboard"
});
@@ -76,21 +77,23 @@ export const ProjectIdentityDetailsSection = ({ identity, membership }: Props) =
<div className="flex items-center justify-between border-b border-mineshaft-400 pb-4">
<h3 className="text-lg font-medium text-mineshaft-100">Identity Details</h3>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
size="xs"
rightIcon={
<FontAwesomeIcon
className="ml-1 transition-transform duration-200 group-data-[state=open]:rotate-180"
icon={faChevronDown}
/>
}
colorSchema="secondary"
className="group select-none"
>
Options
</Button>
</DropdownMenuTrigger>
{!isOrgIdentity && (
<DropdownMenuTrigger asChild>
<Button
size="xs"
rightIcon={
<FontAwesomeIcon
className="ml-1 transition-transform duration-200 group-data-[state=open]:rotate-180"
icon={faChevronDown}
/>
}
colorSchema="secondary"
className="group select-none"
>
Options
</Button>
</DropdownMenuTrigger>
)}
<DropdownMenuContent className="mt-3 min-w-[120px]" align="end">
<ProjectPermissionCan
I={ProjectPermissionIdentityActions.Edit}
@@ -162,25 +165,35 @@ export const ProjectIdentityDetailsSection = ({ identity, membership }: Props) =
</div>
</div>
<div className="mb-4">
<p className="text-sm font-medium text-mineshaft-300">Last Login Auth Method</p>
<p className="text-sm font-medium text-mineshaft-300">Managed By</p>
<p className="text-sm text-mineshaft-300">
{membership.lastLoginAuthMethod
? identityAuthToNameMap[membership.lastLoginAuthMethod]
: "-"}
</p>
</div>
<div className="mb-4">
<p className="text-sm font-medium text-mineshaft-300">Last Login Time</p>
<p className="text-sm text-mineshaft-300">
{membership.lastLoginTime ? format(membership.lastLoginTime, "PPpp") : "-"}
</p>
</div>
<div className="mb-4">
<p className="text-sm font-medium text-mineshaft-300">Delete Protection</p>
<p className="text-sm text-mineshaft-300">
{identity.hasDeleteProtection ? "On" : "Off"}
{identity.projectId ? "Project" : "Organization"}
</p>
</div>
{!isOrgIdentity && (
<>
<div className="mb-4">
<p className="text-sm font-medium text-mineshaft-300">Last Login Auth Method</p>
<p className="text-sm text-mineshaft-300">
{membership.lastLoginAuthMethod
? identityAuthToNameMap[membership.lastLoginAuthMethod]
: "-"}
</p>
</div>
<div className="mb-4">
<p className="text-sm font-medium text-mineshaft-300">Last Login Time</p>
<p className="text-sm text-mineshaft-300">
{membership.lastLoginTime ? format(membership.lastLoginTime, "PPpp") : "-"}
</p>
</div>
<div className="mb-4">
<p className="text-sm font-medium text-mineshaft-300">Delete Protection</p>
<p className="text-sm text-mineshaft-300">
{identity.hasDeleteProtection ? "On" : "Off"}
</p>
</div>
</>
)}
<div>
<p className="text-sm font-medium text-mineshaft-300">Metadata</p>
{identity?.metadata?.length ? (