Przeglądaj źródła

Merge pull request #5813 from weseek/feat/fix-grant-alert-show-alert

feat: Fix grant alert show alert
Yuki Takei 3 lat temu
rodzic
commit
1296af6233

+ 3 - 0
packages/app/src/client/app.jsx

@@ -33,6 +33,7 @@ import GrowiSubNavigationSwitcher from '../components/Navbar/GrowiSubNavigationS
 import NotFoundPage from '../components/NotFoundPage';
 import Page from '../components/Page';
 import DisplaySwitcher from '../components/Page/DisplaySwitcher';
+import FixPageGrantAlert from '../components/Page/FixPageGrantAlert';
 import NotFoundAlert from '../components/Page/NotFoundAlert';
 import RedirectedAlert from '../components/Page/RedirectedAlert';
 import ShareLinkAlert from '../components/Page/ShareLinkAlert';
@@ -99,6 +100,8 @@ Object.assign(componentMappings, {
 
   'trash-page-alert': <TrashPageAlert />,
 
+  'fix-page-grant-alert': <FixPageGrantAlert />,
+
   'trash-page-list-container': <TrashPageList />,
 
   'not-found-page': <NotFoundPage />,

+ 210 - 0
packages/app/src/components/Page/FixPageGrantAlert.tsx

@@ -0,0 +1,210 @@
+import React, { useEffect, useState } from 'react';
+
+import { useTranslation } from 'react-i18next';
+import {
+  Modal, ModalHeader, ModalBody, ModalFooter,
+} from 'reactstrap';
+
+import { toastError, toastSuccess } from '~/client/util/apiNotification';
+import { apiv3Put } from '~/client/util/apiv3-client';
+import { PageGrant } from '~/interfaces/page';
+import { useCurrentPageId } from '~/stores/context';
+import { IResApplicableGrant, useSWRxApplicableGrant, useSWRxIsGrantNormalized } from '~/stores/page';
+
+type ModalProps = {
+  isOpen: boolean
+  pageId: string
+  dataApplicableGrant: IResApplicableGrant
+  close(): void
+}
+
+const FixPageGrantModal = (props: ModalProps): JSX.Element => {
+  const { t } = useTranslation();
+
+  const {
+    isOpen, pageId, dataApplicableGrant, close,
+  } = props;
+
+  const [selectedGrant, setSelectedGrant] = useState<PageGrant>(PageGrant.GRANT_OWNER);
+  const [selectedGroupName, setSelectedGroupName] = useState<string | undefined>(undefined);
+
+  // Alert message state
+  const [shouldShowModalAlert, setShowModalAlert] = useState<boolean>(false);
+
+  const applicableGroups = dataApplicableGrant.data.find(d => d.grant === PageGrant.GRANT_USER_GROUP)?.applicableGroups;
+
+  // Reset state when opened
+  useEffect(() => {
+    if (isOpen) {
+      setSelectedGrant(PageGrant.GRANT_OWNER);
+      setSelectedGroupName(undefined);
+      setShowModalAlert(false);
+    }
+  }, [isOpen]);
+
+  const submit = async() => {
+    // Validate input values
+    if (selectedGrant === PageGrant.GRANT_USER_GROUP && selectedGroupName == null) {
+      setShowModalAlert(true);
+      return;
+    }
+
+    close();
+
+    try {
+      await apiv3Put(`/page/${pageId}/grant`, {
+        grant: selectedGrant,
+        grantedGroupName: selectedGroupName,
+      });
+    }
+    catch (err) {
+      toastError();
+    }
+    toastSuccess();
+  };
+
+  const renderModalBody = () => {
+    const isGrantAvailable = dataApplicableGrant.data.length > 0;
+
+    if (!isGrantAvailable) {
+      return (
+        <p className="mb-2">
+          No grant is available for this page. Please fix the parent page&apos;s grant first.
+        </p>
+      );
+    }
+
+    return (
+      <ModalBody>
+        <div className="form-group grw-scrollable-modal-body">
+          <p className="mb-2">
+            You need to fix the grant of this page. Select new grant from below.
+          </p>
+          <div className="ml-2">
+            <div className="custom-control custom-radio mb-3">
+              <input
+                className="custom-control-input"
+                name="grantUser"
+                id="grantUser"
+                type="radio"
+                checked={selectedGrant === PageGrant.GRANT_OWNER}
+                onChange={() => setSelectedGrant(PageGrant.GRANT_OWNER)}
+              />
+              <label className="custom-control-label" htmlFor="grantUser">
+                { t('fix_page_grant.modal.radio_btn.only_me') }
+              </label>
+            </div>
+            <div className="custom-control custom-radio d-flex mb-3">
+              <input
+                className="custom-control-input"
+                name="grantUserGroup"
+                id="grantUserGroup"
+                type="radio"
+                checked={selectedGrant === PageGrant.GRANT_USER_GROUP}
+                onChange={() => setSelectedGrant(PageGrant.GRANT_USER_GROUP)}
+              />
+              <label className="custom-control-label" htmlFor="grantUserGroup">
+                { t('fix_page_grant.modal.radio_btn.grant_group') }
+              </label>
+              <div className="dropdown ml-2">
+                <div className="d-flex">
+                  <button
+                    type="button"
+                    className="btn btn-secondary dropdown-toggle text-right w-100 border-0 shadow-none"
+                    data-toggle="dropdown"
+                    disabled={selectedGrant !== PageGrant.GRANT_USER_GROUP} // disable when its radio input is not selected
+                  >
+                    <span className="float-left">
+                      {
+                        selectedGroupName == null
+                          ? t('fix_page_grant.modal.select_group_default_text')
+                          : selectedGroupName
+                      }
+                    </span>
+                  </button>
+                  <div className="dropdown-menu">
+                    {
+                      applicableGroups != null && applicableGroups.map(g => (
+                        <button
+                          className="dropdown-item"
+                          type="button"
+                          onClick={() => setSelectedGroupName(g.name)}
+                        >
+                          {g.name}
+                        </button>
+                      ))
+                    }
+                  </div>
+                </div>
+              </div>
+            </div>
+            {
+              shouldShowModalAlert && (
+                <p className="alert alert-warning">
+                  {t('fix_page_grant.modal.alert_message')}
+                </p>
+              )
+            }
+          </div>
+        </div>
+      </ModalBody>
+    );
+  };
+
+  return (
+    <Modal size="lg" isOpen={isOpen} toggle={close} className="grw-create-page">
+      <ModalHeader tag="h4" toggle={close} className="bg-primary text-light">
+        { t('fix_page_grant.modal.title') }
+      </ModalHeader>
+      {renderModalBody()}
+      <ModalFooter>
+        <button type="button" className="btn btn-primary" onClick={submit}>
+          { t('fix_page_grant.modal.button_label') }
+        </button>
+      </ModalFooter>
+    </Modal>
+  );
+};
+
+const FixPageGrantAlert = (): JSX.Element => {
+  const { t } = useTranslation();
+
+  const [isOpen, setOpen] = useState<boolean>(false);
+
+  const { data: pageId } = useCurrentPageId();
+  const { data: dataIsGrantNormalized } = useSWRxIsGrantNormalized(pageId);
+  const { data: dataApplicableGrant } = useSWRxApplicableGrant(pageId);
+
+  if (dataIsGrantNormalized?.isGrantNormalized == null || dataIsGrantNormalized.isGrantNormalized) {
+    return <></>;
+  }
+
+  return (
+    <>
+      <div className="alert alert-warning py-3 pl-4 d-flex flex-column flex-lg-row">
+        <div className="flex-grow-1">
+          <i className="icon-fw icon-exclamation ml-1" aria-hidden="true" />
+          {t('fix_page_grant.alert.description')}
+        </div>
+        <div className="d-flex align-items-end align-items-lg-center">
+          <button type="button" className="btn btn-info btn-sm rounded-pill" onClick={() => setOpen(true)}>
+            {t('fix_page_grant.alert.btn_label')}
+          </button>
+        </div>
+      </div>
+
+      {
+        pageId != null && dataApplicableGrant != null && (
+          <FixPageGrantModal
+            isOpen={isOpen}
+            pageId={pageId}
+            dataApplicableGrant={dataApplicableGrant}
+            close={() => setOpen(false)}
+          />
+        )
+      }
+    </>
+  );
+};
+
+export default FixPageGrantAlert;

+ 10 - 1
packages/app/src/interfaces/page.ts

@@ -18,7 +18,7 @@ export interface IPage {
   parent: Ref<IPage> | null,
   descendantCount: number,
   isEmpty: boolean,
-  grant: number,
+  grant: PageGrant,
   grantedUsers: Ref<IUser>[],
   grantedGroup: Ref<any>,
   lastUpdateUser: Ref<IUser>,
@@ -32,6 +32,15 @@ export interface IPage {
   deletedAt: Date,
 }
 
+export const PageGrant = {
+  GRANT_PUBLIC: 1,
+  GRANT_RESTRICTED: 2,
+  GRANT_SPECIFIED: 3,
+  GRANT_OWNER: 4,
+  GRANT_USER_GROUP: 5,
+};
+export type PageGrant = typeof PageGrant[keyof typeof PageGrant];
+
 export type IPageHasId = IPage & HasObjectId;
 
 export type IPageForItem = Partial<IPageHasId & {isTarget?: boolean}>;

+ 32 - 1
packages/app/src/stores/page.tsx

@@ -5,7 +5,7 @@ import useSWRImmutable from 'swr/immutable';
 import { apiv3Get } from '~/client/util/apiv3-client';
 import { HasObjectId } from '~/interfaces/has-object-id';
 import {
-  IPageInfo, IPageHasId, IPageInfoForOperation, IPageInfoForListing, IDataWithMeta,
+  IPageInfo, IPageHasId, IPageInfoForOperation, IPageInfoForListing, IDataWithMeta, PageGrant,
 } from '~/interfaces/page';
 import { IPagingResult } from '~/interfaces/paging-result';
 
@@ -146,3 +146,34 @@ export const useSWRxPageInfoForList = (
     },
   };
 };
+
+/*
+ * Grant normalization fetching hooks
+ */
+export type IResIsGrantNormalized = { isGrantNormalized: boolean };
+export const useSWRxIsGrantNormalized = (
+    pageId: string | null | undefined,
+): SWRResponse<IResIsGrantNormalized, Error> => {
+
+  return useSWRImmutable(
+    pageId != null ? ['/page/is-grant-normalized', pageId] : null,
+    (endpoint, pageId) => apiv3Get(endpoint, { pageId }).then(response => response.data),
+  );
+};
+
+export type IApplicableGrant = {
+  grant: PageGrant
+  applicableGroups?: {_id: string, name: string}[] // TODO: Typescriptize model
+}
+export type IResApplicableGrant = {
+  data: IApplicableGrant[]
+}
+export const useSWRxApplicableGrant = (
+    pageId: string | null | undefined,
+): SWRResponse<IResApplicableGrant, Error> => {
+
+  return useSWRImmutable(
+    pageId != null ? ['/page/applicable-grant', pageId] : null,
+    (endpoint, pageId) => apiv3Get(endpoint, { pageId }).then(response => response.data),
+  );
+};