Skip to content

Commit

Permalink
Introduce PAT Creation
Browse files Browse the repository at this point in the history
  • Loading branch information
lucemans committed Dec 26, 2024
1 parent 71d0653 commit 3ef6f20
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 0 deletions.
8 changes: 8 additions & 0 deletions web/src/api/user/pat.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { queryOptions, useMutation, useQuery } from '@tanstack/react-query';

import { queryClient } from '@/util/query';

import { apiRequest } from '../core';
import { components } from '../schema.gen';

export const getUserPats = (user_id: number) =>
queryOptions({
Expand All @@ -16,6 +19,9 @@ export const getUserPats = (user_id: number) =>

export const useUserPats = (user_id: number) => useQuery(getUserPats(user_id));


export type PatCreateResult = components['schemas']['CreateKeyResponse'];

export const useCreateUserPat = (user_id: number) =>
useMutation({
mutationFn: async (data: { name: string; permissions: string }) => {
Expand All @@ -25,6 +31,8 @@ export const useCreateUserPat = (user_id: number) =>
data,
});

queryClient.invalidateQueries({ queryKey: ['user_pats'] });

return response.data;
},
});
Expand Down
25 changes: 25 additions & 0 deletions web/src/components/pat/CreatePatButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useState } from 'react';

import { Button } from '../ui/Button';
import { Dialog, DialogTrigger } from '../ui/Dialog';
import { CreatePatModal } from './CreatePatModal';

export const CreatePatButton = () => {
const [open, setOpen] = useState(false);

return (
<Dialog open={open} onOpenChange={(open) => {
setOpen(open);
}}>
<DialogTrigger asChild>
<Button>
Create
</Button>
</DialogTrigger>
{
open &&
<CreatePatModal />
}
</Dialog>
);
};
100 changes: 100 additions & 0 deletions web/src/components/pat/CreatePatModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { useForm } from '@tanstack/react-form';
import { useState } from 'react';
import { toast } from 'sonner';

import { useMe } from '@/api/me';
import { PatCreateResult, useCreateUserPat } from '@/api/user/pat';

import { BaseInput } from '../input/BaseInput';
import { Button } from '../ui/Button';
import { DialogClose, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '../ui/Dialog';


export const CreatePatModal = () => {
const { data: user } = useMe();
const { mutateAsync: createPat } = useCreateUserPat(user?.user_id ?? 0);
const [tokenData, setTokenData] = useState<PatCreateResult | null>();
const { Field, Subscribe, handleSubmit } = useForm({
defaultValues: {
name: '',
},
onSubmit: async (values) => {
console.log(values);
const pat = await createPat({
name: values.value.name,
permissions: 'read',
});

toast.success(`PAT ${pat.key.name}#${pat.key.token_id} created successfully`);

setTokenData(pat);
},
});

return (
<DialogContent>
<DialogHeader>
<DialogTitle>Create PAT</DialogTitle>
</DialogHeader>
<DialogDescription>
Personal Access Tokens are used to authenticate requests to the API.
</DialogDescription>
{
!tokenData &&

<form onSubmit={(event) => {
event.preventDefault();
handleSubmit();
}}>
<div className="space-y-4">
<Field name="name"
validators={{
onChange: ({ value }) => {
if (value.length < 3) {
return 'Name must be at least 3 characters';
}
},
}}
>
{({ handleChange, state }) => <BaseInput
name="name"
label="Name"
placeholder="My Operator"
value={state.value}
onChange={handleChange}
errorMessage={state.meta.errors.join(', ')}
required
/>}
</Field>
</div>
<DialogFooter>
<Subscribe>
{({ isValid }) => (
<Button type="submit" disabled={!isValid}>Create</Button>
)}
</Subscribe>
</DialogFooter>
</form>
}
{
tokenData &&
<>
<div>
<p>Your new has personal access token "{tokenData.key.name}" has been created. Below you will find the token, you will only be able to view this once so make sure to save it somewhere. You can always roll a token if you lose it.</p>
<BaseInput
name="token"
label="Token"
value={tokenData.token}
readOnly
/>
</div>
<DialogFooter>
<DialogClose asChild>
<Button>Close</Button>
</DialogClose>
</DialogFooter>
</>
}
</DialogContent>
);
};
2 changes: 2 additions & 0 deletions web/src/routes/settings/_layout/pat.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { createFileRoute } from '@tanstack/react-router';

import { CreatePatButton } from '@/components/pat/CreatePatButton';
import { UserApiKeysTable } from '@/components/user_api_keys/UserApiKeysTable';

export const Route = createFileRoute('/settings/_layout/pat')({
component: RouteComponent,
context() {
return {
title: 'Personal Access Tokens',
suffix: <CreatePatButton />,
};
},
});
Expand Down

0 comments on commit 3ef6f20

Please sign in to comment.