135 lines
4.2 KiB
TypeScript
135 lines
4.2 KiB
TypeScript
import { ArrowPathIcon } from "@heroicons/react/20/solid";
|
|
import { useFetcher } from "@remix-run/react";
|
|
import { useState } from "react";
|
|
import { Dialog, DialogContent, DialogHeader, DialogTrigger } from "~/components/primitives/Dialog";
|
|
import { generateTwoRandomWords } from "~/utils/randomWords";
|
|
import { Button } from "../primitives/Buttons";
|
|
import { Callout } from "../primitives/Callout";
|
|
import { Fieldset } from "../primitives/Fieldset";
|
|
import { FormButtons } from "../primitives/FormButtons";
|
|
import { Input } from "../primitives/Input";
|
|
import { InputGroup } from "../primitives/InputGroup";
|
|
import { Paragraph } from "../primitives/Paragraph";
|
|
import { CheckboxWithLabel } from "../primitives/Checkbox";
|
|
import { Spinner } from "../primitives/Spinner";
|
|
|
|
type ModalProps = {
|
|
id: string;
|
|
title: string;
|
|
hasVercelIntegration: boolean;
|
|
isDevelopment: boolean;
|
|
};
|
|
|
|
type ModalContentProps = ModalProps & {
|
|
randomWord: string;
|
|
closeModal: () => void;
|
|
};
|
|
|
|
export function RegenerateApiKeyModal({
|
|
id,
|
|
title,
|
|
hasVercelIntegration,
|
|
isDevelopment,
|
|
}: ModalProps) {
|
|
const randomWord = generateTwoRandomWords();
|
|
const [open, setOpen] = useState(false);
|
|
return (
|
|
<Dialog open={open} onOpenChange={setOpen}>
|
|
<DialogTrigger asChild>
|
|
<Button variant="minimal/small" textAlignLeft LeadingIcon={ArrowPathIcon}>
|
|
Regenerate…
|
|
</Button>
|
|
</DialogTrigger>
|
|
<DialogContent>
|
|
<DialogHeader>{`Regenerate ${title} environment key`}</DialogHeader>
|
|
<RegenerateApiKeyModalContent
|
|
id={id}
|
|
title={title}
|
|
hasVercelIntegration={hasVercelIntegration}
|
|
isDevelopment={isDevelopment}
|
|
randomWord={randomWord}
|
|
closeModal={() => setOpen(false)}
|
|
/>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|
|
|
|
const RegenerateApiKeyModalContent = ({
|
|
id,
|
|
randomWord,
|
|
title,
|
|
hasVercelIntegration,
|
|
isDevelopment,
|
|
closeModal,
|
|
}: ModalContentProps) => {
|
|
const [confirmationText, setConfirmationText] = useState("");
|
|
const fetcher = useFetcher();
|
|
const isSubmitting = fetcher.state === "submitting";
|
|
|
|
// form submission completed
|
|
if (fetcher.state === "loading") {
|
|
closeModal();
|
|
}
|
|
|
|
return (
|
|
<div className="flex flex-col items-center gap-y-4 pt-4">
|
|
<Callout variant="warning">
|
|
{`A new API key will be issued for the ${title} environment. The previous key stays valid
|
|
for 24 hours so you can roll out the new key in your environment variables without downtime.
|
|
After 24 hours, the previous key stops working.`}
|
|
</Callout>
|
|
<fetcher.Form
|
|
method="post"
|
|
action={`/resources/environments/${id}/regenerate-api-key`}
|
|
className="mt-2 w-full"
|
|
>
|
|
<Fieldset className="w-full">
|
|
<InputGroup className="max-w-full">
|
|
<Paragraph variant="small/bright">Enter this text below to confirm:</Paragraph>
|
|
<Paragraph
|
|
variant="small"
|
|
className="select-all rounded-md border border-grid-bright bg-background-deep px-2 py-1 font-mono"
|
|
>
|
|
{randomWord}
|
|
</Paragraph>
|
|
<Input
|
|
type="text"
|
|
placeholder="Confirmation text"
|
|
fullWidth
|
|
value={confirmationText}
|
|
onChange={(e) => setConfirmationText(e.target.value)}
|
|
/>
|
|
</InputGroup>
|
|
{hasVercelIntegration && !isDevelopment && (
|
|
<CheckboxWithLabel
|
|
name="syncToVercel"
|
|
variant="simple/small"
|
|
label="Also update TRIGGER_SECRET_KEY in Vercel"
|
|
defaultChecked={true}
|
|
value="on"
|
|
/>
|
|
)}
|
|
<FormButtons
|
|
confirmButton={
|
|
<Button
|
|
type="submit"
|
|
variant={"primary/medium"}
|
|
LeadingIcon={isSubmitting ? Spinner : undefined}
|
|
disabled={confirmationText !== randomWord}
|
|
>
|
|
Regenerate
|
|
</Button>
|
|
}
|
|
cancelButton={
|
|
<Button variant={"tertiary/medium"} type="button" onClick={closeModal}>
|
|
Cancel
|
|
</Button>
|
|
}
|
|
/>
|
|
</Fieldset>
|
|
</fetcher.Form>
|
|
</div>
|
|
);
|
|
};
|