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