Files
Reactive-Resume/src/dialogs/api-key/create.tsx
T
Amruth Pillai 5cd16a62d9 v5.0.12 (#2814)
* refactor to @base-ui/react

* fix all

* fixes to accordion

* more updates

* switch to chat/completions api from openai

* update version to v5.0.12
2026-03-17 23:38:06 +01:00

213 lines
5.8 KiB
TypeScript

import { zodResolver } from "@hookform/resolvers/zod";
import { t } from "@lingui/core/macro";
import { Trans } from "@lingui/react/macro";
import { CopyIcon, PlusIcon } from "@phosphor-icons/react";
import { useQueryClient } from "@tanstack/react-query";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { useCopyToClipboard } from "usehooks-ts";
import z from "zod";
import { Button } from "@/components/ui/button";
import { Combobox } from "@/components/ui/combobox";
import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { InputGroup, InputGroupAddon, InputGroupButton, InputGroupInput } from "@/components/ui/input-group";
import { useFormBlocker } from "@/hooks/use-form-blocker";
import { authClient } from "@/integrations/auth/client";
import { type DialogProps, useDialogStore } from "../store";
const formSchema = z.object({
name: z.string().min(1).max(64),
expiresIn: z.number().min(1),
});
type FormValues = z.infer<typeof formSchema>;
export function CreateApiKeyDialog(_: DialogProps<"api-key.create">) {
const [apiKey, setApiKey] = useState<string | null>(null);
if (apiKey) return <CopyApiKeyForm apiKey={apiKey} />;
return <CreateApiKeyForm setApiKey={setApiKey} />;
}
type CreateApiKeyFormProps = {
setApiKey: (apiKey: string) => void;
};
const CreateApiKeyForm = ({ setApiKey }: CreateApiKeyFormProps) => {
const form = useForm<FormValues>({
resolver: zodResolver(formSchema),
defaultValues: {
name: "",
expiresIn: 3600 * 24 * 30,
},
});
const { blockEvents } = useFormBlocker(form);
const onSubmit = async (values: FormValues) => {
const toastId = toast.loading(t`Creating your API key...`);
const { data, error } = await authClient.apiKey.create({
name: values.name,
expiresIn: values.expiresIn,
});
if (error) {
toast.error(error.message, { id: toastId });
return;
}
setApiKey(data.key);
toast.dismiss(toastId);
};
return (
<DialogContent {...blockEvents}>
<DialogHeader>
<DialogTitle className="flex items-center gap-x-2">
<PlusIcon />
<Trans>Create a new API key</Trans>
</DialogTitle>
<DialogDescription>
<Trans>
This will generate a new API key to access the Reactive Resume API to allow machines to interact with your
resume data.
</Trans>
</DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6 py-2">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Name</Trans>
</FormLabel>
<FormControl render={<Input min={1} max={64} {...field} />} />
<FormMessage />
<FormDescription>
<Trans>
Tip: Give your API key a name, corresponding to the purpose of the key, to help you identify it
later.
</Trans>
</FormDescription>
</FormItem>
)}
/>
<FormField
control={form.control}
name="expiresIn"
render={({ field }) => (
<FormItem>
<FormLabel>
<Trans>Expires in</Trans>
</FormLabel>
<FormControl
render={
<Combobox
value={field.value}
onValueChange={(value) => value && field.onChange(Number(value))}
options={[
{
// 1 month = 30 days
value: 3600 * 24 * 30,
label: t`1 month`,
},
{
// 3 months = 90 days
value: 3600 * 24 * 90,
label: t`3 months`,
},
{
// 6 months = 180 days
value: 3600 * 24 * 180,
label: t`6 months`,
},
{
// 1 year = 365 days
value: 3600 * 24 * 365,
label: t`1 year`,
},
]}
/>
}
/>
<FormMessage />
</FormItem>
)}
/>
<DialogFooter>
<Button type="submit">
<Trans>Create</Trans>
</Button>
</DialogFooter>
</form>
</Form>
</DialogContent>
);
};
type CopyApiKeyFormProps = {
apiKey: string;
};
const CopyApiKeyForm = ({ apiKey }: CopyApiKeyFormProps) => {
const queryClient = useQueryClient();
const [_, copyToClipboard] = useCopyToClipboard();
const closeDialog = useDialogStore((state) => state.closeDialog);
const onCopy = () => {
copyToClipboard(apiKey);
toast.success(t`Your API key has been copied to the clipboard.`);
};
const onConfirm = () => {
closeDialog();
queryClient.invalidateQueries({ queryKey: ["auth", "api-keys"] });
};
return (
<DialogContent>
<DialogHeader>
<DialogTitle className="flex items-center gap-x-2">
<CopyIcon />
<Trans>Here's your new API key</Trans>
</DialogTitle>
<DialogDescription>
<Trans>Copy this secret key and use it in your applications to access your data.</Trans>
</DialogDescription>
</DialogHeader>
<div className="space-y-2 py-2">
<InputGroup>
<InputGroupInput value={apiKey} readOnly />
<InputGroupAddon align="inline-end">
<InputGroupButton size="icon-sm" onClick={onCopy}>
<CopyIcon />
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
<span className="font-medium text-muted-foreground text-sm">
<Trans>For security reasons, this key will only be displayed once.</Trans>
</span>
</div>
<DialogFooter>
<Button onClick={onConfirm}>
<Trans>Confirm</Trans>
</Button>
</DialogFooter>
</DialogContent>
);
};