Просмотр исходного кода

Merge pull request #7875 from weseek/imprv/111416-125483-show-modal-when-click-button

imprv: Show modal when you delete plugin
Ryoji Shimizu 2 лет назад
Родитель
Сommit
cf2025ee66

+ 1 - 1
apps/app/public/static/locales/en_US/admin.json

@@ -871,7 +871,7 @@
     "plugin_card": "Plugin Card",
     "plugin_is_not_installed": "Plugin is not installed",
     "install": "Install",
-    "delete": "Delete"
+    "confirm": "Delete plugin?"
   },
   "cloud_setting_management": {
     "to_cloud_settings": "Open GROWI.cloud Settings"

+ 1 - 1
apps/app/public/static/locales/ja_JP/admin.json

@@ -879,7 +879,7 @@
     "plugin_card": "プラグインカード",
     "plugin_is_not_installed": "プラグインがインストールされていません",
     "install": "インストール",
-    "delete": "削除"
+    "confirm": "プラグインを削除しますか?"
   },
   "cloud_setting_management": {
     "to_cloud_settings": "GROWI.cloud の管理画面へ"

+ 1 - 1
apps/app/public/static/locales/zh_CN/admin.json

@@ -879,7 +879,7 @@
     "plugin_card": "Plugin Card",
     "plugin_is_not_installed": "Plugin is not installed",
     "install": "Install",
-    "delete": "Delete"
+    "confirm": "Delete plugin?"
   },
   "cloud_setting_management": {
     "to_cloud_settings": "進入 GROWI.cloud 的管理界面"

+ 5 - 21
apps/app/src/features/growi-plugin/client/components/Admin/PluginsExtensionPageContents/PluginCard.tsx

@@ -3,7 +3,7 @@ import React, { useState } from 'react';
 import { useTranslation } from 'next-i18next';
 import Link from 'next/link';
 
-import { apiv3Delete, apiv3Put } from '~/client/util/apiv3-client';
+import { apiv3Put } from '~/client/util/apiv3-client';
 import { toastSuccess, toastError } from '~/client/util/toastr';
 
 import styles from './PluginCard.module.scss';
@@ -13,14 +13,14 @@ type Props = {
   name: string,
   url: string,
   isEnalbed: boolean,
-  mutate: () => void,
   desc?: string,
+  onDelete: () => void,
 }
 
 export const PluginCard = (props: Props): JSX.Element => {
 
   const {
-    id, name, url, isEnalbed, desc, mutate,
+    id, name, url, isEnalbed, desc,
   } = props;
 
   const { t } = useTranslation('admin');
@@ -70,30 +70,14 @@ export const PluginCard = (props: Props): JSX.Element => {
 
   const PluginDeleteButton = (): JSX.Element => {
 
-    const onClickPluginDeleteBtnHandler = async() => {
-      const reqUrl = `/plugins/${id}/remove`;
-
-      try {
-        const res = await apiv3Delete(reqUrl);
-        const pluginName = res.data.pluginName;
-        toastSuccess(t('toaster.remove_plugin_success', { pluginName }));
-      }
-      catch (err) {
-        toastError(err);
-      }
-      finally {
-        mutate();
-      }
-    };
-
     return (
       <div className="">
         <button
           type="submit"
           className="btn btn-primary"
-          onClick={() => onClickPluginDeleteBtnHandler()}
+          onClick={props.onDelete}
         >
-          {t('plugins.delete')}
+          {t('Delete')}
         </button>
       </div>
     );

+ 64 - 0
apps/app/src/features/growi-plugin/client/components/Admin/PluginsExtensionPageContents/PluginDeleteModal.tsx

@@ -0,0 +1,64 @@
+import React, { useCallback } from 'react';
+
+import { useTranslation } from 'next-i18next';
+import Link from 'next/link';
+import {
+  Button, Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
+
+import { apiv3Delete } from '~/client/util/apiv3-client';
+import { toastSuccess, toastError } from '~/client/util/toastr';
+
+import { useSWRxAdminPlugins, usePluginDeleteModal } from '../../../stores/admin-plugins';
+
+export const PluginDeleteModal: React.FC = () => {
+
+  const { t } = useTranslation('admin');
+  const { mutate } = useSWRxAdminPlugins();
+  const { data: pluginDeleteModalData, close: closePluginDeleteModal } = usePluginDeleteModal();
+  const isOpen = pluginDeleteModalData?.isOpen;
+  const id = pluginDeleteModalData?.id;
+  const name = pluginDeleteModalData?.name;
+  const url = pluginDeleteModalData?.url;
+
+  const toggleHandler = useCallback(() => {
+    closePluginDeleteModal();
+  }, [closePluginDeleteModal]);
+
+  const onClickDeleteButtonHandler = useCallback(async() => {
+    const reqUrl = `/plugins/${id}/remove`;
+
+    try {
+      const res = await apiv3Delete(reqUrl);
+      const pluginName = res.data.pluginName;
+      closePluginDeleteModal();
+      toastSuccess(t('toaster.remove_plugin_success', { pluginName }));
+      mutate();
+    }
+    catch (err) {
+      toastError(err);
+    }
+  }, [id, closePluginDeleteModal, t, mutate]);
+
+  return (
+    <Modal isOpen={isOpen} toggle={toggleHandler}>
+      <ModalHeader tag="h4" toggle={toggleHandler} className="bg-danger text-light" name={name}>
+        <span>
+          <i className="icon-fw icon-fire"></i>
+          {t('plugins.confirm')}
+        </span>
+      </ModalHeader>
+      <ModalBody>
+        <div className="card well mt-2 p-2" key={id}>
+          <Link href={`${url}`} legacyBehavior>{name}</Link>
+        </div>
+      </ModalBody>
+      <ModalFooter>
+        <Button color="danger" onClick={onClickDeleteButtonHandler}>
+          <i className="icon-fw icon-fire"></i>
+          {t('Delete')}
+        </Button>
+      </ModalFooter>
+    </Modal>
+  );
+};

+ 17 - 20
apps/app/src/features/growi-plugin/client/components/Admin/PluginsExtensionPageContents/PluginsExtensionPageContents.tsx

@@ -1,9 +1,10 @@
 import React from 'react';
 
 import { useTranslation } from 'next-i18next';
+import dynamic from 'next/dynamic';
 import { Spinner } from 'reactstrap';
 
-import { useSWRxAdminPlugins } from '../../../stores/admin-plugins';
+import { useSWRxAdminPlugins, usePluginDeleteModal } from '../../../stores/admin-plugins';
 
 import { PluginCard } from './PluginCard';
 import { PluginInstallerForm } from './PluginInstallerForm';
@@ -18,8 +19,10 @@ const Loading = (): JSX.Element => {
 
 export const PluginsExtensionPageContents = (): JSX.Element => {
   const { t } = useTranslation('admin');
-
+  const PluginDeleteModal = dynamic(() => import('./PluginDeleteModal')
+    .then(mod => mod.PluginDeleteModal), { ssr: false });
   const { data, mutate } = useSWRxAdminPlugins();
+  const { open: openPluginDeleteModal } = usePluginDeleteModal();
 
   return (
     <div>
@@ -45,28 +48,22 @@ export const PluginsExtensionPageContents = (): JSX.Element => {
                 { data.plugins.length === 0 && (
                   <div>{t('plugins.plugin_is_not_installed')}</div>
                 )}
-                { data.plugins.map((plugin) => {
-                  const pluginId = plugin._id;
-                  const pluginName = plugin.meta.name;
-                  const pluginUrl = plugin.origin.url;
-                  const pluginIsEnabled = plugin.isEnabled;
-                  const pluginDiscription = plugin.meta.desc;
-                  return (
-                    <PluginCard
-                      key={pluginId}
-                      id={pluginId}
-                      name={pluginName}
-                      url={pluginUrl}
-                      isEnalbed={pluginIsEnabled}
-                      desc={pluginDiscription}
-                      mutate={mutate}
-                    />
-                  );
-                })}
+                {data.plugins.map(plugin => (
+                  <PluginCard
+                    key={plugin._id}
+                    id={plugin._id}
+                    name={plugin.meta.name}
+                    url={plugin.origin.url}
+                    isEnalbed={plugin.isEnabled}
+                    desc={plugin.meta.desc}
+                    onDelete={() => openPluginDeleteModal(plugin)}
+                  />
+                ))}
               </div>
             )}
         </div>
       </div>
+      <PluginDeleteModal />
 
     </div>
   );

+ 47 - 0
apps/app/src/features/growi-plugin/client/stores/admin-plugins.tsx

@@ -1,6 +1,7 @@
 import useSWR, { SWRResponse } from 'swr';
 
 import { apiv3Get } from '~/client/util/apiv3-client';
+import { useStaticSWR } from '~/stores/use-static-swr';
 
 import type { IGrowiPluginHasId } from '../../interfaces';
 
@@ -22,3 +23,49 @@ export const useSWRxAdminPlugins = (): SWRResponse<Plugins, Error> => {
     },
   );
 };
+
+/*
+ * PluginDeleteModal
+ */
+type PluginDeleteModalStatus = {
+  isOpen: boolean,
+  id: string,
+  name: string,
+  url: string,
+}
+
+type PluginDeleteModalUtils = {
+  open(plugin: IGrowiPluginHasId): Promise<void>,
+  close(): Promise<void>,
+}
+
+export const usePluginDeleteModal = (): SWRResponse<PluginDeleteModalStatus, Error> & PluginDeleteModalUtils => {
+  const initialStatus: PluginDeleteModalStatus = {
+    isOpen: false,
+    id: '',
+    name: '',
+    url: '',
+  };
+
+  const swrResponse = useStaticSWR<PluginDeleteModalStatus, Error>('pluginDeleteModal', undefined, { fallbackData: initialStatus });
+  const { mutate } = swrResponse;
+
+  const open = async(plugin) => {
+    mutate({
+      isOpen: true,
+      id: plugin._id,
+      name: plugin.meta.name,
+      url: plugin.origin.url,
+    });
+  };
+
+  const close = async() => {
+    mutate(initialStatus);
+  };
+
+  return {
+    ...swrResponse,
+    open,
+    close,
+  };
+};