mirror of
https://github.com/graphql-hive/console
synced 2026-05-23 17:18:23 +00:00
Drop modals from V2: Create Access Token Modal (#5244)
This commit is contained in:
parent
2b6313a564
commit
ecdf2a13a4
6 changed files with 441 additions and 254 deletions
|
|
@ -0,0 +1,319 @@
|
|||
import { useState } from 'react';
|
||||
import { useForm, UseFormReturn } from 'react-hook-form';
|
||||
import { AnyVariables, useMutation, UseMutationState, useQuery } from 'urql';
|
||||
import { z } from 'zod';
|
||||
import { Tag } from '@/components//v2/tag';
|
||||
import { PermissionScopeItem, usePermissionsManager } from '@/components/organization/Permissions';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { useToast } from '@/components/ui/use-toast';
|
||||
import { Accordion } from '@/components/v2/accordion';
|
||||
import { CopyValue } from '@/components/v2/copy-value';
|
||||
import { FragmentType, graphql, useFragment } from '@/gql';
|
||||
import { TargetAccessScope } from '@/gql/graphql';
|
||||
import { RegistryAccessScope } from '@/lib/access/common';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
|
||||
export const CreateAccessToken_CreateTokenMutation = graphql(`
|
||||
mutation CreateAccessToken_CreateToken($input: CreateTokenInput!) {
|
||||
createToken(input: $input) {
|
||||
ok {
|
||||
selector {
|
||||
organization
|
||||
project
|
||||
target
|
||||
}
|
||||
createdToken {
|
||||
id
|
||||
name
|
||||
alias
|
||||
date
|
||||
lastUsedAt
|
||||
}
|
||||
secret
|
||||
}
|
||||
error {
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const CreateAccessTokenModalQuery = graphql(`
|
||||
query CreateAccessTokenModalQuery($organizationId: ID!) {
|
||||
organization(selector: { organization: $organizationId }) {
|
||||
organization {
|
||||
...CreateAccessTokenModalContent_OrganizationFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export function CreateAccessTokenModal(props: {
|
||||
isOpen: boolean;
|
||||
toggleModalOpen: () => void;
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
targetId: string;
|
||||
}) {
|
||||
const { isOpen, toggleModalOpen } = props;
|
||||
const [organizationQuery] = useQuery({
|
||||
query: CreateAccessTokenModalQuery,
|
||||
variables: {
|
||||
organizationId: props.organizationId,
|
||||
},
|
||||
});
|
||||
|
||||
const organization = organizationQuery.data?.organization?.organization;
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={toggleModalOpen}>
|
||||
{organization ? (
|
||||
<ModalContent
|
||||
organization={organization}
|
||||
organizationId={props.organizationId}
|
||||
projectId={props.projectId}
|
||||
targetId={props.targetId}
|
||||
toggleModalOpen={toggleModalOpen}
|
||||
/>
|
||||
) : (
|
||||
<DialogContent className="container w-4/5 max-w-[600px] md:w-3/5">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Organization not found</DialogTitle>
|
||||
<DialogDescription>
|
||||
The organization you are trying to access does not exist.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button onClick={toggleModalOpen}>Ok, got it!</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
)}
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
|
||||
const CreateAccessTokenModalContent_OrganizationFragment = graphql(`
|
||||
fragment CreateAccessTokenModalContent_OrganizationFragment on Organization {
|
||||
id
|
||||
...UsePermissionManager_OrganizationFragment
|
||||
me {
|
||||
...UsePermissionManager_MemberFragment
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
function getFinalTargetAccessScopes(
|
||||
selectedScope: 'no-access' | TargetAccessScope,
|
||||
): Array<TargetAccessScope> {
|
||||
if (selectedScope === 'no-access') {
|
||||
return [];
|
||||
}
|
||||
if (selectedScope === TargetAccessScope.RegistryWrite) {
|
||||
return [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite];
|
||||
}
|
||||
return [TargetAccessScope.RegistryRead];
|
||||
}
|
||||
|
||||
const createRegistryTokenFormSchema = z.object({
|
||||
tokenDescription: z
|
||||
.string({
|
||||
required_error: 'Token description is required',
|
||||
})
|
||||
.min(2, {
|
||||
message: 'Token description must be at least 2 characters long',
|
||||
})
|
||||
.max(50, {
|
||||
message: 'Token description must be at most 50 characters long',
|
||||
})
|
||||
.regex(
|
||||
/^([a-z]|[0-9]|\s|\.|,|_|-|\/|&)+$/i,
|
||||
'Token description restricted to alphanumerical characters, spaces and . , _ - / &',
|
||||
),
|
||||
});
|
||||
|
||||
export function ModalContent(props: {
|
||||
organization: FragmentType<typeof CreateAccessTokenModalContent_OrganizationFragment>;
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
targetId: string;
|
||||
toggleModalOpen: () => void;
|
||||
}) {
|
||||
const { toast } = useToast();
|
||||
const organization = useFragment(
|
||||
CreateAccessTokenModalContent_OrganizationFragment,
|
||||
props.organization,
|
||||
);
|
||||
const [selectedScope, setSelectedScope] = useState<'no-access' | TargetAccessScope>('no-access');
|
||||
|
||||
const manager = usePermissionsManager({
|
||||
onSuccess() {},
|
||||
organization,
|
||||
member: organization.me,
|
||||
passMemberScopes: false,
|
||||
});
|
||||
|
||||
const form = useForm<z.infer<typeof createRegistryTokenFormSchema>>({
|
||||
mode: 'onChange',
|
||||
resolver: zodResolver(createRegistryTokenFormSchema),
|
||||
defaultValues: {
|
||||
tokenDescription: '',
|
||||
},
|
||||
});
|
||||
|
||||
const [mutation, mutate] = useMutation(CreateAccessToken_CreateTokenMutation);
|
||||
|
||||
async function onSubmit(values: z.infer<typeof createRegistryTokenFormSchema>) {
|
||||
const { error } = await mutate({
|
||||
input: {
|
||||
organization: props.organizationId,
|
||||
project: props.projectId,
|
||||
target: props.targetId,
|
||||
name: values.tokenDescription,
|
||||
organizationScopes: [],
|
||||
projectScopes: [],
|
||||
targetScopes: getFinalTargetAccessScopes(selectedScope),
|
||||
},
|
||||
});
|
||||
if (error) {
|
||||
toast({
|
||||
variant: 'destructive',
|
||||
title: 'Failed to create token',
|
||||
description: error.message,
|
||||
});
|
||||
} else {
|
||||
toast({
|
||||
title: 'Token created',
|
||||
description: 'The token has been successfully created.',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const noPermissionsSelected = selectedScope === 'no-access';
|
||||
if (mutation.data?.createToken.ok) {
|
||||
return <CreatedTokenContent mutation={mutation} toggleModalOpen={props.toggleModalOpen} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<GenerateTokenContent
|
||||
form={form}
|
||||
manager={manager}
|
||||
noPermissionsSelected={noPermissionsSelected}
|
||||
onSubmit={onSubmit}
|
||||
selectedScope={selectedScope} // Ensure selectedScope is passed correctly
|
||||
setSelectedScope={setSelectedScope}
|
||||
toggleModalOpen={props.toggleModalOpen}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export function CreatedTokenContent(props: {
|
||||
mutation: UseMutationState<any, AnyVariables>;
|
||||
toggleModalOpen: () => void;
|
||||
}) {
|
||||
return (
|
||||
<DialogContent className="container w-4/5 max-w-[600px] md:w-3/5">
|
||||
<DialogHeader className="flex flex-col gap-5">
|
||||
<DialogTitle>Token successfully created!</DialogTitle>
|
||||
<DialogDescription className="flex flex-col gap-5">
|
||||
<CopyValue value={props.mutation.data.createToken.ok.secret} />
|
||||
<Tag color="green">
|
||||
This is your unique API key and it is non-recoverable. If you lose this key, you will
|
||||
need to create a new one.
|
||||
</Tag>
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button onClick={props.toggleModalOpen}>Ok, got it!</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
|
||||
export function GenerateTokenContent(props: {
|
||||
form: UseFormReturn<z.infer<typeof createRegistryTokenFormSchema>>;
|
||||
onSubmit: (values: z.infer<typeof createRegistryTokenFormSchema>) => void;
|
||||
manager: ReturnType<typeof usePermissionsManager>;
|
||||
setSelectedScope: (scope: 'no-access' | TargetAccessScope) => void;
|
||||
selectedScope: 'no-access' | TargetAccessScope;
|
||||
toggleModalOpen: () => void;
|
||||
noPermissionsSelected: boolean;
|
||||
}) {
|
||||
return (
|
||||
<DialogContent className="container w-4/5 max-w-[600px] md:w-3/5">
|
||||
<Form {...props.form}>
|
||||
<form
|
||||
className="flex grow flex-col gap-5"
|
||||
onSubmit={props.form.handleSubmit(props.onSubmit)}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Create an access token</DialogTitle>
|
||||
<DialogDescription>
|
||||
To access GraphQL Hive, your application or tool needs an active API key.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<FormField
|
||||
control={props.form.control}
|
||||
name="tokenDescription"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormControl>
|
||||
<Input placeholder="Token description" autoComplete="off" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
<Accordion defaultValue="Permissions">
|
||||
<Accordion.Item value="Permissions">
|
||||
<Accordion.Header>Registry & Usage</Accordion.Header>
|
||||
<Accordion.Content>
|
||||
<PermissionScopeItem
|
||||
key={props.selectedScope}
|
||||
scope={RegistryAccessScope}
|
||||
canManageScope={
|
||||
props.manager.canAccessTarget(RegistryAccessScope.mapping['read-only']) ||
|
||||
props.manager.canAccessTarget(RegistryAccessScope.mapping['read-write'])
|
||||
}
|
||||
checkAccess={props.manager.canAccessTarget}
|
||||
onChange={value => {
|
||||
if (value === 'no-access') {
|
||||
props.setSelectedScope('no-access');
|
||||
return;
|
||||
}
|
||||
props.setSelectedScope(value);
|
||||
}}
|
||||
possibleScope={Object.values(RegistryAccessScope.mapping)}
|
||||
initialScope={props.selectedScope}
|
||||
selectedScope={props.selectedScope} // Ensure selectedScope is passed correctly
|
||||
/>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" type="button" onClick={props.toggleModalOpen}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={!props.form.formState.isValid || props.noPermissionsSelected}
|
||||
>
|
||||
Generate Token
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Form>
|
||||
</DialogContent>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,251 +0,0 @@
|
|||
import { ReactElement, useState } from 'react';
|
||||
import { useFormik } from 'formik';
|
||||
import { useMutation, useQuery } from 'urql';
|
||||
import * as Yup from 'yup';
|
||||
import { PermissionScopeItem, usePermissionsManager } from '@/components/organization/Permissions';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Heading } from '@/components/ui/heading';
|
||||
import { Accordion, CopyValue, Input, Modal, Tag } from '@/components/v2';
|
||||
import { FragmentType, graphql, useFragment } from '@/gql';
|
||||
import { TargetAccessScope } from '@/gql/graphql';
|
||||
import { RegistryAccessScope } from '@/lib/access/common';
|
||||
|
||||
export const CreateAccessToken_CreateTokenMutation = graphql(`
|
||||
mutation CreateAccessToken_CreateToken($input: CreateTokenInput!) {
|
||||
createToken(input: $input) {
|
||||
ok {
|
||||
selector {
|
||||
organization
|
||||
project
|
||||
target
|
||||
}
|
||||
createdToken {
|
||||
id
|
||||
name
|
||||
alias
|
||||
date
|
||||
lastUsedAt
|
||||
}
|
||||
secret
|
||||
}
|
||||
error {
|
||||
message
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
const CreateAccessTokenModalQuery = graphql(`
|
||||
query CreateAccessTokenModalQuery($organizationId: ID!) {
|
||||
organization(selector: { organization: $organizationId }) {
|
||||
organization {
|
||||
...CreateAccessTokenModalContent_OrganizationFragment
|
||||
}
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export function CreateAccessTokenModal(props: {
|
||||
isOpen: boolean;
|
||||
toggleModalOpen: () => void;
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
targetId: string;
|
||||
}): ReactElement {
|
||||
const { isOpen, toggleModalOpen } = props;
|
||||
const [organizationQuery] = useQuery({
|
||||
query: CreateAccessTokenModalQuery,
|
||||
variables: {
|
||||
organizationId: props.organizationId,
|
||||
},
|
||||
});
|
||||
|
||||
const organization = organizationQuery.data?.organization?.organization;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={isOpen}
|
||||
onOpenChange={toggleModalOpen}
|
||||
className="flex h-5/6 w-[650px] overflow-hidden"
|
||||
>
|
||||
{organization ? (
|
||||
<ModalContent
|
||||
organization={organization}
|
||||
organizationId={props.organizationId}
|
||||
projectId={props.projectId}
|
||||
targetId={props.targetId}
|
||||
toggleModalOpen={toggleModalOpen}
|
||||
/>
|
||||
) : null}
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
const CreateAccessTokenModalContent_OrganizationFragment = graphql(`
|
||||
fragment CreateAccessTokenModalContent_OrganizationFragment on Organization {
|
||||
id
|
||||
...UsePermissionManager_OrganizationFragment
|
||||
me {
|
||||
...UsePermissionManager_MemberFragment
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
function getFinalTargetAccessScopes(
|
||||
selectedScope: 'no-access' | TargetAccessScope,
|
||||
): Array<TargetAccessScope> {
|
||||
if (selectedScope === 'no-access') {
|
||||
return [];
|
||||
}
|
||||
/** When RegistryWrite got selected, we also need to provide RegistryRead. */
|
||||
if (selectedScope === TargetAccessScope.RegistryWrite) {
|
||||
return [TargetAccessScope.RegistryRead, TargetAccessScope.RegistryWrite];
|
||||
}
|
||||
return [TargetAccessScope.RegistryRead];
|
||||
}
|
||||
|
||||
function ModalContent(props: {
|
||||
organization: FragmentType<typeof CreateAccessTokenModalContent_OrganizationFragment>;
|
||||
organizationId: string;
|
||||
projectId: string;
|
||||
targetId: string;
|
||||
toggleModalOpen: () => void;
|
||||
}): ReactElement {
|
||||
const organization = useFragment(
|
||||
CreateAccessTokenModalContent_OrganizationFragment,
|
||||
props.organization,
|
||||
);
|
||||
const [selectedScope, setSelectedScope] = useState(
|
||||
'no-access' as TargetAccessScope | 'no-access',
|
||||
);
|
||||
|
||||
const manager = usePermissionsManager({
|
||||
onSuccess() {},
|
||||
organization,
|
||||
member: organization.me,
|
||||
passMemberScopes: false,
|
||||
});
|
||||
|
||||
const [mutation, mutate] = useMutation(CreateAccessToken_CreateTokenMutation);
|
||||
const { handleSubmit, values, handleChange, handleBlur, isSubmitting, errors, touched } =
|
||||
useFormik({
|
||||
initialValues: { name: '' },
|
||||
validationSchema: Yup.object().shape({
|
||||
name: Yup.string().required('Must enter description'),
|
||||
}),
|
||||
async onSubmit(values) {
|
||||
await mutate({
|
||||
input: {
|
||||
organization: props.organizationId,
|
||||
project: props.projectId,
|
||||
target: props.targetId,
|
||||
name: values.name,
|
||||
organizationScopes: [],
|
||||
projectScopes: [],
|
||||
targetScopes: getFinalTargetAccessScopes(selectedScope),
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const noPermissionsSelected = selectedScope === 'no-access';
|
||||
|
||||
return (
|
||||
<>
|
||||
{mutation.data?.createToken.ok ? (
|
||||
<div className="flex grow flex-col gap-5">
|
||||
<Heading className="text-center">Token successfully created!</Heading>
|
||||
<CopyValue value={mutation.data.createToken.ok.secret} />
|
||||
<Tag color="green">
|
||||
This is your unique API key and it is non-recoverable. If you lose this key, you will
|
||||
need to create a new one.
|
||||
</Tag>
|
||||
<div className="grow" />
|
||||
<Button variant="primary" size="lg" className="ml-auto" onClick={props.toggleModalOpen}>
|
||||
Ok, got it!
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<form className="flex grow flex-col gap-5" onSubmit={handleSubmit}>
|
||||
<div className="shrink-0">
|
||||
<div className="flex-none">
|
||||
<Heading className="mb-2 text-center">Create an access token</Heading>
|
||||
<p className="mb-2 text-sm text-gray-500">
|
||||
To access GraphQL Hive, your application or tool needs an active API key.
|
||||
</p>
|
||||
|
||||
<Input
|
||||
placeholder="Token description"
|
||||
name="name"
|
||||
value={values.name}
|
||||
onChange={handleChange}
|
||||
onBlur={handleBlur}
|
||||
disabled={isSubmitting}
|
||||
isInvalid={touched.name && !!errors.name}
|
||||
className="w-full"
|
||||
/>
|
||||
</div>
|
||||
{touched.name && errors.name && (
|
||||
<div className="mt-2 text-sm text-red-500">{errors.name}</div>
|
||||
)}
|
||||
{mutation.data?.createToken.error && (
|
||||
<div className="mt-2 text-sm text-red-500">
|
||||
{mutation.data?.createToken.error.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-1 flex-col overflow-hidden">
|
||||
<Accordion defaultValue="Permissions">
|
||||
<Accordion.Item value="Permissions">
|
||||
<Accordion.Header>Registry & Usage</Accordion.Header>
|
||||
<Accordion.Content>
|
||||
<PermissionScopeItem
|
||||
scope={RegistryAccessScope}
|
||||
canManageScope={
|
||||
manager.canAccessTarget(RegistryAccessScope.mapping['read-only']) ||
|
||||
manager.canAccessTarget(RegistryAccessScope.mapping['read-write'])
|
||||
}
|
||||
checkAccess={manager.canAccessTarget}
|
||||
onChange={value => {
|
||||
if (value === 'no-access') {
|
||||
setSelectedScope('no-access');
|
||||
return;
|
||||
}
|
||||
setSelectedScope(value);
|
||||
}}
|
||||
possibleScope={Object.values(RegistryAccessScope.mapping)}
|
||||
initialScope={selectedScope}
|
||||
selectedScope={selectedScope}
|
||||
/>
|
||||
</Accordion.Content>
|
||||
</Accordion.Item>
|
||||
</Accordion>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
{mutation.error && <div className="text-sm text-red-500">{mutation.error.message}</div>}
|
||||
|
||||
<div className="flex w-full gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
size="lg"
|
||||
className="w-full justify-center"
|
||||
onClick={props.toggleModalOpen}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
size="lg"
|
||||
className="w-full justify-center"
|
||||
variant="primary"
|
||||
disabled={isSubmitting || noPermissionsSelected}
|
||||
>
|
||||
Generate Token
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
export { ChangePermissionsModal } from './change-permissions';
|
||||
export { ConnectSchemaModal } from './connect-schema';
|
||||
export { CreateAccessTokenModal } from './create-access-token';
|
||||
export { DeleteOrganizationModal } from './delete-organization';
|
||||
export { DeleteProjectModal } from './delete-project';
|
||||
export { DeleteTargetModal } from './delete-target';
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import type { DeleteChannelsButton_DeleteChannelsMutation } from '@/components/p
|
|||
import type { CreateOperationMutationType } from '@/components/target/laboratory/create-operation-modal';
|
||||
import type { DeleteCollectionMutationType } from '@/components/target/laboratory/delete-collection-modal';
|
||||
import type { DeleteOperationMutationType } from '@/components/target/laboratory/delete-operation-modal';
|
||||
import type { CreateAccessToken_CreateTokenMutation } from '@/components/v2/modals/create-access-token';
|
||||
import type { CreateAccessToken_CreateTokenMutation } from '@/components/target/settings/registry-access-token';
|
||||
import type { DeleteOrganizationDocument } from '@/components/v2/modals/delete-organization';
|
||||
import { type DeleteProjectMutation } from '@/components/v2/modals/delete-project';
|
||||
import { type DeleteTargetMutation } from '@/components/v2/modals/delete-target';
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import * as Yup from 'yup';
|
|||
import { Page, TargetLayout } from '@/components/layouts/target';
|
||||
import { SchemaEditor } from '@/components/schema-editor';
|
||||
import { CDNAccessTokens } from '@/components/target/settings/cdn-access-tokens';
|
||||
import { CreateAccessTokenModal } from '@/components/target/settings/registry-access-token';
|
||||
import { SchemaContracts } from '@/components/target/settings/schema-contracts';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { CardDescription } from '@/components/ui/card';
|
||||
|
|
@ -26,7 +27,7 @@ import { TimeAgo } from '@/components/ui/time-ago';
|
|||
import { useToast } from '@/components/ui/use-toast';
|
||||
import { Combobox } from '@/components/v2/combobox';
|
||||
import { Input } from '@/components/v2/input';
|
||||
import { CreateAccessTokenModal, DeleteTargetModal } from '@/components/v2/modals';
|
||||
import { DeleteTargetModal } from '@/components/v2/modals';
|
||||
import { Switch } from '@/components/v2/switch';
|
||||
import { Table, TBody, Td, Tr } from '@/components/v2/table';
|
||||
import { Tag } from '@/components/v2/tag';
|
||||
|
|
|
|||
119
packages/web/app/src/stories/registry-access-token.stories.tsx
Normal file
119
packages/web/app/src/stories/registry-access-token.stories.tsx
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
import { useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { usePermissionsManager } from '@/components/organization/Permissions';
|
||||
import {
|
||||
CreatedTokenContent,
|
||||
GenerateTokenContent,
|
||||
} from '@/components/target/settings/registry-access-token';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Dialog, DialogTrigger } from '@/components/ui/dialog';
|
||||
import { TargetAccessScope } from '@/gql/graphql';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { Meta, StoryObj } from '@storybook/react';
|
||||
|
||||
const meta: Meta<typeof CreatedTokenContent> = {
|
||||
title: 'Modals/Create Access Token',
|
||||
component: CreatedTokenContent,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
type Story = StoryObj<typeof CreatedTokenContent>;
|
||||
|
||||
const formSchema = z.object({
|
||||
tokenDescription: z
|
||||
.string()
|
||||
.min(2, { message: 'Token description must be at least 2 characters long' }),
|
||||
});
|
||||
|
||||
export const GenerateToken: Story = {
|
||||
render: () => {
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const toggleModalOpen = () => setOpenModal(!openModal);
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
mode: 'onChange',
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
tokenDescription: '',
|
||||
},
|
||||
});
|
||||
|
||||
const manager = {
|
||||
canAccessOrganization: () => true,
|
||||
canAccessProject: () => true,
|
||||
canAccessTarget: () => true,
|
||||
noneSelected: false,
|
||||
organizationScopes: [],
|
||||
projectScopes: [],
|
||||
targetScopes: [],
|
||||
setOrganizationScopes: () => {},
|
||||
setProjectScopes: () => {},
|
||||
setTargetScopes: () => {},
|
||||
submit: () => {},
|
||||
} as unknown as ReturnType<typeof usePermissionsManager>;
|
||||
|
||||
const [selectedScope, setSelectedScope] = useState<'no-access' | TargetAccessScope>(
|
||||
'no-access',
|
||||
);
|
||||
const noPermissionsSelected = selectedScope === 'no-access';
|
||||
|
||||
return (
|
||||
<Dialog open={openModal} onOpenChange={toggleModalOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button onClick={toggleModalOpen}>Open Modal</Button>
|
||||
</DialogTrigger>
|
||||
<GenerateTokenContent
|
||||
form={form}
|
||||
manager={manager}
|
||||
noPermissionsSelected={noPermissionsSelected}
|
||||
onSubmit={values => console.log('Submit:', values)}
|
||||
selectedScope={selectedScope}
|
||||
setSelectedScope={setSelectedScope}
|
||||
toggleModalOpen={toggleModalOpen}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export const CreatedToken: Story = {
|
||||
render: () => {
|
||||
const [openModal, setOpenModal] = useState(false);
|
||||
const toggleModalOpen = () => setOpenModal(!openModal);
|
||||
|
||||
return (
|
||||
<Dialog open={openModal} onOpenChange={toggleModalOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button onClick={toggleModalOpen}>Open Modal</Button>
|
||||
</DialogTrigger>
|
||||
<CreatedTokenContent
|
||||
mutation={{
|
||||
fetching: false,
|
||||
data: {
|
||||
createToken: {
|
||||
ok: {
|
||||
selector: {
|
||||
organization: '',
|
||||
project: '',
|
||||
target: '',
|
||||
},
|
||||
createdToken: {
|
||||
id: 'token-id',
|
||||
name: 'Token Name',
|
||||
alias: 'token-alias',
|
||||
date: '2023-07-17',
|
||||
lastUsedAt: null,
|
||||
},
|
||||
secret: 'token-secret',
|
||||
},
|
||||
},
|
||||
},
|
||||
stale: false,
|
||||
}}
|
||||
toggleModalOpen={toggleModalOpen}
|
||||
/>
|
||||
</Dialog>
|
||||
);
|
||||
},
|
||||
};
|
||||
Loading…
Reference in a new issue