openhands / src /components /features /plugins /add-plugin-modal.tsx
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
9b906ea verified
Raw
History Blame Contribute Delete
3.97 kB
import React from "react";
import { useTranslation } from "react-i18next";
import { ModalBackdrop } from "#/components/shared/modals/modal-backdrop";
import { ModalCloseButton } from "#/components/shared/modals/modal-close-button";
import { BrandButton } from "#/components/features/settings/brand-button";
import { SettingsInput } from "#/components/features/settings/settings-input";
import { useInstallPlugin } from "#/hooks/mutation/use-install-plugin";
import { I18nKey } from "#/i18n/declaration";
import { cn } from "#/utils/utils";
import { modalTitleLgClassName } from "#/utils/modal-classes";
interface AddPluginModalProps {
onClose: () => void;
onInstalled?: () => void;
}
export function AddPluginModal({ onClose, onInstalled }: AddPluginModalProps) {
const { t } = useTranslation("openhands");
const installPlugin = useInstallPlugin();
const [source, setSource] = React.useState("");
const [ref, setRef] = React.useState("");
const [repoPath, setRepoPath] = React.useState("");
const trimmedSource = source.trim();
const canSubmit = trimmedSource.length > 0 && !installPlugin.isPending;
const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
if (!canSubmit) return;
installPlugin.mutate(
{
source: trimmedSource,
ref: ref.trim() || null,
repo_path: repoPath.trim() || null,
},
{
onSuccess: () => {
onInstalled?.();
onClose();
},
},
);
};
return (
<ModalBackdrop
onClose={onClose}
aria-label={t(I18nKey.SETTINGS$PLUGINS_ADD_MODAL_TITLE)}
>
<form
onSubmit={handleSubmit}
data-testid="add-plugin-modal"
className="relative flex w-[520px] max-w-[90vw] max-h-[85vh] flex-col rounded-xl border border-[var(--oh-border)] bg-base-secondary"
>
<ModalCloseButton onClose={onClose} testId="add-plugin-modal-close" />
<header className="flex-shrink-0 px-6 pb-4 pt-6">
<h2 className={cn("pr-6", modalTitleLgClassName)}>
{t(I18nKey.SETTINGS$PLUGINS_ADD_MODAL_TITLE)}
</h2>
<p className="mt-4 text-sm text-tertiary-light">
{t(I18nKey.SETTINGS$PLUGINS_ADD_MODAL_INTRO)}
</p>
</header>
<div className="flex min-h-0 flex-1 flex-col gap-4 overflow-y-auto px-6 custom-scrollbar">
<SettingsInput
testId="add-plugin-source-input"
label={t(I18nKey.SETTINGS$PLUGINS_SOURCE_LABEL)}
type="text"
value={source}
onChange={setSource}
placeholder={t(I18nKey.SETTINGS$PLUGINS_SOURCE_PLACEHOLDER)}
showRequiredTag
/>
<SettingsInput
testId="add-plugin-ref-input"
label={t(I18nKey.SETTINGS$PLUGINS_REF_LABEL)}
type="text"
value={ref}
onChange={setRef}
showOptionalTag
/>
<SettingsInput
testId="add-plugin-repo-path-input"
label={t(I18nKey.SETTINGS$PLUGINS_REPO_PATH_LABEL)}
type="text"
value={repoPath}
onChange={setRepoPath}
showOptionalTag
/>
</div>
<footer className="flex flex-shrink-0 justify-end gap-2 px-6 pb-6 pt-4">
<BrandButton
type="button"
variant="secondary"
onClick={onClose}
testId="add-plugin-modal-dismiss"
>
{t(I18nKey.BUTTON$CLOSE)}
</BrandButton>
<BrandButton
type="submit"
variant="primary"
testId="add-plugin-submit"
isDisabled={!canSubmit}
>
{t(
installPlugin.isPending
? I18nKey.SETTINGS$PLUGINS_INSTALLING
: I18nKey.SETTINGS$PLUGINS_INSTALL,
)}
</BrandButton>
</footer>
</form>
</ModalBackdrop>
);
}