Files
2026-07-13 13:32:57 +08:00

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>
);
};