mirror of
https://github.com/graphql-hive/console
synced 2026-05-23 17:18:23 +00:00
(4) Drop modals from V2: change permissions Modal (#5233)
Co-authored-by: Kamil Kisiela <kamil.kisiela@gmail.com>
This commit is contained in:
parent
00926e750c
commit
dc4da0d32f
9 changed files with 234 additions and 116 deletions
|
|
@ -3,6 +3,7 @@ import { MoreHorizontalIcon, MoveDownIcon, MoveUpIcon, SettingsIcon } from 'luci
|
|||
import type { IconType } from 'react-icons';
|
||||
import { FaGithub, FaGoogle, FaOpenid, FaUserLock } from 'react-icons/fa';
|
||||
import { useMutation } from 'urql';
|
||||
import { PermissionsSpace, usePermissionsManager } from '@/components/organization/Permissions';
|
||||
import {
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
|
|
@ -14,6 +15,13 @@ import {
|
|||
AlertDialogTitle,
|
||||
} from '@/components/ui/alert-dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
|
|
@ -21,11 +29,17 @@ import {
|
|||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { SubPageLayout, SubPageLayoutHeader } from '@/components/ui/page-content-layout';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
import { ChangePermissionsModal } from '@/components/v2/modals';
|
||||
import { FragmentType, graphql, useFragment } from '@/gql';
|
||||
import { AuthProvider } from '@/gql/graphql';
|
||||
import {
|
||||
AuthProvider,
|
||||
OrganizationAccessScope,
|
||||
ProjectAccessScope,
|
||||
TargetAccessScope,
|
||||
} from '@/gql/graphql';
|
||||
import { scopes } from '@/lib/access/common';
|
||||
import { useToggle } from '@/lib/hooks';
|
||||
import { RoleSelector } from './common';
|
||||
import { MemberInvitationButton } from './invitations';
|
||||
|
|
@ -267,8 +281,8 @@ function OrganizationMemberRoleSwitcher(props: {
|
|||
<ChangePermissionsModal
|
||||
isOpen={isPermissionsModalOpen}
|
||||
toggleModalOpen={togglePermissionsModalOpen}
|
||||
organization={organization}
|
||||
member={member}
|
||||
organizationFragment={organization}
|
||||
memberFragment={member}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
|
|
@ -587,3 +601,118 @@ export function OrganizationMembers(props: {
|
|||
</SubPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const ChangePermissionsModal_OrganizationFragment = graphql(`
|
||||
fragment ChangePermissionsModal_OrganizationFragment on Organization {
|
||||
...UsePermissionManager_OrganizationFragment
|
||||
}
|
||||
`);
|
||||
|
||||
export const ChangePermissionsModal_MemberFragment = graphql(`
|
||||
fragment ChangePermissionsModal_MemberFragment on Member {
|
||||
id
|
||||
...UsePermissionManager_MemberFragment
|
||||
}
|
||||
`);
|
||||
|
||||
export function ChangePermissionsModal(props: {
|
||||
isOpen: boolean;
|
||||
toggleModalOpen: () => void;
|
||||
organizationFragment: FragmentType<typeof ChangePermissionsModal_OrganizationFragment>;
|
||||
memberFragment: FragmentType<typeof ChangePermissionsModal_MemberFragment>;
|
||||
}) {
|
||||
const organization = useFragment(
|
||||
ChangePermissionsModal_OrganizationFragment,
|
||||
props.organizationFragment,
|
||||
);
|
||||
const member = useFragment(ChangePermissionsModal_MemberFragment, props.memberFragment);
|
||||
const manager = usePermissionsManager({
|
||||
onSuccess: props.toggleModalOpen,
|
||||
organization,
|
||||
member,
|
||||
passMemberScopes: true,
|
||||
});
|
||||
|
||||
const initialScopes = {
|
||||
organization: [...manager.organizationScopes],
|
||||
project: [...manager.projectScopes],
|
||||
target: [...manager.targetScopes],
|
||||
};
|
||||
|
||||
return (
|
||||
<ChangePermissionsModalContent
|
||||
isOpen={props.isOpen}
|
||||
toggleModalOpen={props.toggleModalOpen}
|
||||
manager={manager}
|
||||
initialScopes={initialScopes}
|
||||
onSubmit={() => manager.submit}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function ChangePermissionsModalContent(props: {
|
||||
isOpen: boolean;
|
||||
toggleModalOpen: () => void;
|
||||
manager: ReturnType<typeof usePermissionsManager>;
|
||||
initialScopes: {
|
||||
organization: OrganizationAccessScope[];
|
||||
project: ProjectAccessScope[];
|
||||
target: TargetAccessScope[];
|
||||
};
|
||||
onSubmit: () => void;
|
||||
}) {
|
||||
return (
|
||||
<Dialog open={props.isOpen} onOpenChange={props.toggleModalOpen}>
|
||||
<DialogContent className="w-4/5 max-w-[750px] md:w-3/5">
|
||||
<form className="flex w-full flex-col gap-5" onSubmit={props.onSubmit}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Permissions (legacy)</DialogTitle>
|
||||
</DialogHeader>
|
||||
<Tabs defaultValue="Organization" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="Organization">Organization</TabsTrigger>
|
||||
<TabsTrigger value="Projects">Projects</TabsTrigger>
|
||||
<TabsTrigger value="Targets">Targets</TabsTrigger>
|
||||
</TabsList>
|
||||
<PermissionsSpace
|
||||
title="Organization"
|
||||
scopes={scopes.organization}
|
||||
initialScopes={props.initialScopes.organization}
|
||||
selectedScopes={props.manager.organizationScopes}
|
||||
onChange={props.manager.setOrganizationScopes}
|
||||
checkAccess={props.manager.canAccessOrganization}
|
||||
/>
|
||||
<PermissionsSpace
|
||||
title="Projects"
|
||||
scopes={scopes.project}
|
||||
initialScopes={props.initialScopes.project}
|
||||
selectedScopes={props.manager.projectScopes}
|
||||
onChange={props.manager.setProjectScopes}
|
||||
checkAccess={props.manager.canAccessProject}
|
||||
/>
|
||||
<PermissionsSpace
|
||||
title="Targets"
|
||||
scopes={scopes.target}
|
||||
initialScopes={props.initialScopes.target}
|
||||
selectedScopes={props.manager.targetScopes}
|
||||
onChange={props.manager.setTargetScopes}
|
||||
checkAccess={props.manager.canAccessTarget}
|
||||
/>
|
||||
</Tabs>
|
||||
<DialogFooter className="gap-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={ev => {
|
||||
ev.preventDefault();
|
||||
props.toggleModalOpen();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit">Save permissions</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,14 @@ function CreateCDNAccessTokenModal(props: {
|
|||
</div>
|
||||
|
||||
<div className="mt-auto flex w-full gap-2 self-end">
|
||||
<Button variant="secondary" className="ml-auto" onClick={props.onClose}>
|
||||
<Button
|
||||
variant="secondary"
|
||||
className="ml-auto"
|
||||
onClick={ev => {
|
||||
ev.preventDefault();
|
||||
props.onClose();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
|
||||
|
|
|
|||
|
|
@ -302,7 +302,14 @@ export function GenerateTokenContent(props: {
|
|||
</Accordion>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" type="button" onClick={props.toggleModalOpen}>
|
||||
<Button
|
||||
variant="outline"
|
||||
type="button"
|
||||
onClick={ev => {
|
||||
ev.preventDefault();
|
||||
props.toggleModalOpen();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
import { ReactElement } from 'react';
|
||||
import { PermissionsSpace, usePermissionsManager } from '@/components/organization/Permissions';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Heading } from '@/components/ui/heading';
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
||||
import { Modal } from '@/components/v2';
|
||||
import { FragmentType, graphql, useFragment } from '@/gql';
|
||||
import { scopes } from '@/lib/access/common';
|
||||
|
||||
const ChangePermissionsModal_OrganizationFragment = graphql(`
|
||||
fragment ChangePermissionsModal_OrganizationFragment on Organization {
|
||||
...UsePermissionManager_OrganizationFragment
|
||||
}
|
||||
`);
|
||||
|
||||
const ChangePermissionsModal_MemberFragment = graphql(`
|
||||
fragment ChangePermissionsModal_MemberFragment on Member {
|
||||
id
|
||||
...UsePermissionManager_MemberFragment
|
||||
}
|
||||
`);
|
||||
|
||||
export function ChangePermissionsModal({
|
||||
isOpen,
|
||||
toggleModalOpen,
|
||||
...props
|
||||
}: {
|
||||
isOpen: boolean;
|
||||
toggleModalOpen: () => void;
|
||||
organization: FragmentType<typeof ChangePermissionsModal_OrganizationFragment>;
|
||||
member: FragmentType<typeof ChangePermissionsModal_MemberFragment>;
|
||||
}): ReactElement {
|
||||
const organization = useFragment(ChangePermissionsModal_OrganizationFragment, props.organization);
|
||||
const member = useFragment(ChangePermissionsModal_MemberFragment, props.member);
|
||||
const manager = usePermissionsManager({
|
||||
onSuccess: toggleModalOpen,
|
||||
organization,
|
||||
member,
|
||||
passMemberScopes: true,
|
||||
});
|
||||
|
||||
const initialScopes = {
|
||||
organization: [...manager.organizationScopes],
|
||||
project: [...manager.projectScopes],
|
||||
target: [...manager.targetScopes],
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal open={isOpen} onOpenChange={toggleModalOpen} className="w-[600px]">
|
||||
<form className="flex w-full flex-col items-center gap-5" onSubmit={manager.submit}>
|
||||
<Heading>Permissions (legacy)</Heading>
|
||||
<Tabs defaultValue="Organization" className="w-full">
|
||||
<TabsList className="grid w-full grid-cols-3">
|
||||
<TabsTrigger value="Organization">Organization</TabsTrigger>
|
||||
<TabsTrigger value="Projects">Projects</TabsTrigger>
|
||||
<TabsTrigger value="Targets">Targets</TabsTrigger>
|
||||
</TabsList>
|
||||
<PermissionsSpace
|
||||
title="Organization"
|
||||
scopes={scopes.organization}
|
||||
initialScopes={initialScopes.organization}
|
||||
selectedScopes={manager.organizationScopes}
|
||||
onChange={manager.setOrganizationScopes}
|
||||
checkAccess={manager.canAccessOrganization}
|
||||
/>
|
||||
<PermissionsSpace
|
||||
title="Projects"
|
||||
scopes={scopes.project}
|
||||
initialScopes={initialScopes.project}
|
||||
selectedScopes={manager.projectScopes}
|
||||
onChange={manager.setProjectScopes}
|
||||
checkAccess={manager.canAccessProject}
|
||||
/>
|
||||
<PermissionsSpace
|
||||
title="Targets"
|
||||
scopes={scopes.target}
|
||||
initialScopes={initialScopes.target}
|
||||
selectedScopes={manager.targetScopes}
|
||||
onChange={manager.setTargetScopes}
|
||||
checkAccess={manager.canAccessTarget}
|
||||
/>
|
||||
</Tabs>
|
||||
<div className="flex w-full gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="lg"
|
||||
className="w-full justify-center"
|
||||
onClick={toggleModalOpen}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button type="submit" size="lg" className="w-full justify-center" variant="primary">
|
||||
Save permissions
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,3 +1,2 @@
|
|||
export { ChangePermissionsModal } from './change-permissions';
|
||||
export { ConnectSchemaModal } from './connect-schema';
|
||||
export { TransferOrganizationOwnershipModal } from './transfer-organization-ownership';
|
||||
|
|
|
|||
|
|
@ -692,7 +692,13 @@ export function DeleteOrganizationModalContent(props: {
|
|||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="gap-2">
|
||||
<Button variant="outline" onClick={props.toggleModalOpen}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={ev => {
|
||||
ev.preventDefault();
|
||||
props.toggleModalOpen();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={props.handleDelete}>
|
||||
|
|
|
|||
|
|
@ -520,7 +520,13 @@ export function DeleteProjectModalContent(props: {
|
|||
<DialogDescription className="font-bold">This action is irreversible!</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="gap-2">
|
||||
<Button variant="outline" onClick={props.toggleModalOpen}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={ev => {
|
||||
ev.preventDefault();
|
||||
props.toggleModalOpen();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={props.handleDelete}>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { ReactElement, useCallback, useState } from 'react';
|
||||
import { ComponentProps, PropsWithoutRef, useCallback, useState } from 'react';
|
||||
import clsx from 'clsx';
|
||||
import { formatISO } from 'date-fns';
|
||||
import { useFormik } from 'formik';
|
||||
|
|
@ -111,7 +111,7 @@ function RegistryAccessTokens(props: {
|
|||
organizationId: string;
|
||||
projectId: string;
|
||||
targetId: string;
|
||||
}): ReactElement {
|
||||
}) {
|
||||
const me = useFragment(RegistryAccessTokens_MeFragment, props.me);
|
||||
const [{ fetching: deleting }, mutate] = useMutation(DeleteTokensDocument);
|
||||
const [checked, setChecked] = useState<string[]>([]);
|
||||
|
|
@ -242,7 +242,7 @@ const ExtendBaseSchema = (props: {
|
|||
organizationId: string;
|
||||
projectId: string;
|
||||
targetId: string;
|
||||
}): ReactElement => {
|
||||
}) => {
|
||||
const [mutation, mutate] = useMutation(Settings_UpdateBaseSchemaMutation);
|
||||
const [baseSchema, setBaseSchema] = useState(props.baseSchema);
|
||||
const { toast } = useToast();
|
||||
|
|
@ -342,14 +342,14 @@ const ClientExclusion_AvailableClientNamesQuery = graphql(`
|
|||
`);
|
||||
|
||||
function ClientExclusion(
|
||||
props: React.PropsWithoutRef<
|
||||
props: PropsWithoutRef<
|
||||
{
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
selectedTargets: string[];
|
||||
clientsFromSettings: string[];
|
||||
value: string[];
|
||||
} & Pick<React.ComponentProps<typeof Combobox>, 'name' | 'disabled' | 'onBlur' | 'onChange'>
|
||||
} & Pick<ComponentProps<typeof Combobox>, 'name' | 'disabled' | 'onBlur' | 'onChange'>
|
||||
>,
|
||||
) {
|
||||
const now = floorDate(new Date());
|
||||
|
|
@ -461,7 +461,7 @@ const ConditionalBreakingChanges = (props: {
|
|||
organizationId: string;
|
||||
projectId: string;
|
||||
targetId: string;
|
||||
}): ReactElement => {
|
||||
}) => {
|
||||
const [targetValidation, setValidation] = useMutation(SetTargetValidationMutation);
|
||||
const [mutation, updateValidation] = useMutation(
|
||||
TargetSettingsPage_UpdateTargetValidationSettingsMutation,
|
||||
|
|
@ -875,7 +875,7 @@ function GraphQLEndpointUrl(props: {
|
|||
organizationId: string;
|
||||
projectId: string;
|
||||
targetId: string;
|
||||
}): ReactElement {
|
||||
}) {
|
||||
const { toast } = useToast();
|
||||
const [mutation, mutate] = useMutation(TargetSettingsPage_UpdateTargetGraphQLEndpointUrl);
|
||||
const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } =
|
||||
|
|
@ -1364,7 +1364,13 @@ export function DeleteTargetModalContent(props: {
|
|||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="gap-2">
|
||||
<Button variant="outline" onClick={props.toggleModalOpen}>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={ev => {
|
||||
ev.preventDefault();
|
||||
props.toggleModalOpen();
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant="destructive" onClick={props.handleDelete}>
|
||||
|
|
|
|||
57
packages/web/app/src/stories/change-permissions.stories.tsx
Normal file
57
packages/web/app/src/stories/change-permissions.stories.tsx
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import { useState } from 'react';
|
||||
import { ChangePermissionsModalContent } from '@/components/organization/members/list';
|
||||
import { usePermissionsManager } from '@/components/organization/Permissions';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
const meta: Meta<typeof ChangePermissionsModalContent> = {
|
||||
title: 'Modals/Change Permissions Modal',
|
||||
component: ChangePermissionsModalContent,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof ChangePermissionsModalContent>;
|
||||
|
||||
export const Default: Story = {
|
||||
render: () => {
|
||||
const initialScopes = {
|
||||
organization: [],
|
||||
project: [],
|
||||
target: [],
|
||||
};
|
||||
|
||||
const manager = {
|
||||
organizationScopes: [],
|
||||
projectScopes: [],
|
||||
targetScopes: [],
|
||||
canAccessOrganization: () => true,
|
||||
canAccessProject: () => true,
|
||||
canAccessTarget: () => true,
|
||||
noneSelected: true,
|
||||
setOrganizationScopes: () => console.log('Set Organization Scopes'),
|
||||
setProjectScopes: () => console.log('Set Project Scopes'),
|
||||
setTargetScopes: () => console.log('Set Target Scopes'),
|
||||
state: 'IDLE',
|
||||
submit: () => console.log('Submit'),
|
||||
} as ReturnType<typeof usePermissionsManager>;
|
||||
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const toggleModalOpen = () => setOpenModal(!openModal);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button onClick={toggleModalOpen}>Open Modal</Button>
|
||||
{openModal && (
|
||||
<ChangePermissionsModalContent
|
||||
manager={manager}
|
||||
isOpen={openModal}
|
||||
toggleModalOpen={toggleModalOpen}
|
||||
initialScopes={initialScopes}
|
||||
onSubmit={() => console.log('Submit')}
|
||||
key="change-permissions-modal"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
},
|
||||
};
|
||||
Loading…
Reference in a new issue