File size: 3,972 Bytes
9b906ea | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | 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>
);
}
|