Răsfoiți Sursa

refs 113224: apply feedbac

Futa Arai 3 ani în urmă
părinte
comite
bea7dff5c2

+ 1 - 1
packages/app/public/static/locales/ja_JP/translation.json

@@ -818,7 +818,7 @@
     "failed_to_send": "回答送信に失敗しました",
     "failed_to_get_user_info": "ユーザ情報の取得に失敗したため、回答を送信できませんでした",
     "failed_to_update_answer_status": "アンケートの回答状態の更新に失敗しました",
-    "skipped": "アンケートはスキップされ、今後表示されません"
+    "skipped": "このアンケートはスキップされたため、今後表示されません"
   },
   "tag_edit_modal": {
     "edit_tags": "タグの編集",

+ 50 - 45
packages/app/src/components/Questionnaire/QuestionnaireModal.tsx

@@ -1,3 +1,5 @@
+import { useCallback } from 'react';
+
 import { useTranslation } from 'next-i18next';
 import {
   Modal, ModalHeader, ModalBody, ModalFooter,
@@ -10,7 +12,6 @@ import { IGrowiInfo } from '~/interfaces/questionnaire/growi-info';
 import { IQuestionnaireAnswer } from '~/interfaces/questionnaire/questionnaire-answer';
 import { IQuestionnaireOrderHasId } from '~/interfaces/questionnaire/questionnaire-order';
 import { IUserInfo } from '~/interfaces/questionnaire/user-info';
-import { IUserHasId } from '~/interfaces/user';
 import { useCurrentUser, useGrowiVersion } from '~/stores/context';
 import { useQuestionnaireModal } from '~/stores/modal';
 import axios from '~/utils/axios';
@@ -28,7 +29,6 @@ type QuestionnaireModalProps = {
 const QuestionnaireModal = ({ questionnaireOrder, growiQuestionnaireServerOrigin }: QuestionnaireModalProps): JSX.Element => {
   const { data: currentUser } = useCurrentUser();
   const lang = currentUser?.lang;
-  const currentUserHasId = currentUser as IUserHasId;
 
   const { data: questionnaireModalData, close: closeQuestionnaireModal } = useQuestionnaireModal();
   const isOpened = questionnaireModalData?.openedQuestionnaireId === questionnaireOrder._id;
@@ -40,7 +40,7 @@ const QuestionnaireModal = ({ questionnaireOrder, growiQuestionnaireServerOrigin
   const { t } = useTranslation();
 
   // TODO: モック化されている箇所を実装
-  const getGrowiInfo = (): IGrowiInfo => {
+  const getGrowiInfo = useCallback((): IGrowiInfo => {
     return {
       version: growiVersion || '',
       osInfo: {
@@ -58,10 +58,10 @@ const QuestionnaireModal = ({ questionnaireOrder, growiQuestionnaireServerOrigin
       activeExternalAccountTypes: 'sample account type',
       deploymentType: 'official-helm-chart',
     };
-  };
+  }, [growiVersion]);
 
   // TODO: モック化されている箇所を実装
-  const getUserInfo = (): IUserInfo | null => {
+  const getUserInfo = useCallback((): IUserInfo | null => {
     if (currentUser) {
       return {
         userIdHash: '542bcc3bc5bc61b840017a18',
@@ -70,36 +70,40 @@ const QuestionnaireModal = ({ questionnaireOrder, growiQuestionnaireServerOrigin
       };
     }
     return null;
-  };
-
-  const sendQuestionnaireAnswer = (questionnaireAnswer: IQuestionnaireAnswer) => {
-    axios.post(`${growiQuestionnaireServerOrigin}/questionnaire-answer`, questionnaireAnswer)
-      .then(() => {
-        toastSuccess(
-          <>
-            <div className="font-weight-bold">{t('questionnaire.thank_you_for_answering')}</div>
-            <div className="pt-2">{t('questionnaire.additional_feedback')}</div>
-          </>,
-          {
-            autoClose: 3000,
-            closeButton: true,
-          },
-        );
-        apiv3Put('/questionnaire/answer', {
-          user: currentUserHasId._id,
-          questionnaireOrderId: questionnaireOrder._id,
-        }).catch((e) => {
-          logger.error(e);
-          toastError(t('questionnaire.failed_to_update_answer_status'));
-        });
-      })
-      .catch((e) => {
-        logger.error(e);
-        toastError(t('questionnaire.failed_to_send'));
+  }, [currentUser]);
+
+  const sendQuestionnaireAnswer = useCallback(async(questionnaireAnswer: IQuestionnaireAnswer) => {
+    try {
+      await axios.post(`${growiQuestionnaireServerOrigin}/questionnaire-answer`, questionnaireAnswer);
+      toastSuccess(
+        <>
+          <div className="font-weight-bold">{t('questionnaire.thank_you_for_answering')}</div>
+          <div className="pt-2">{t('questionnaire.additional_feedback')}</div>
+        </>,
+        {
+          autoClose: 3000,
+          closeButton: true,
+        },
+      );
+    }
+    catch (e) {
+      logger.error(e);
+      toastError(t('questionnaire.failed_to_send'));
+    }
+
+    try {
+      await apiv3Put('/questionnaire/answer', {
+        user: currentUser?._id,
+        questionnaireOrderId: questionnaireOrder._id,
       });
-  };
+    }
+    catch (e) {
+      logger.error(e);
+      toastError(t('questionnaire.failed_to_update_answer_status'));
+    }
+  }, [currentUser?._id, growiQuestionnaireServerOrigin, questionnaireOrder._id, t]);
 
-  const submitHandler = (event) => {
+  const submitHandler = useCallback((event) => {
     event.preventDefault();
 
     const growiInfo = getGrowiInfo();
@@ -124,21 +128,22 @@ const QuestionnaireModal = ({ questionnaireOrder, growiQuestionnaireServerOrigin
     }
 
     closeQuestionnaireModal();
-  };
+  }, [closeQuestionnaireModal, getGrowiInfo, getUserInfo, t, questionnaireOrder.questions, sendQuestionnaireAnswer]);
 
-  const skipHandler = async() => {
-    apiv3Put('/questionnaire/skip', {
-      user: currentUserHasId._id,
-      questionnaireOrderId: questionnaireOrder._id,
-    }).then(() => {
+  const skipBtnClickHandler = useCallback(async() => {
+    try {
+      apiv3Put('/questionnaire/skip', {
+        user: currentUser?._id,
+        questionnaireOrderId: questionnaireOrder._id,
+      });
       toastSuccess(t('questionnaire.skipped'));
-    }).catch((e) => {
+    }
+    catch (e) {
       logger.error(e);
       toastError(t('questionnaire.failed_to_update_answer_status'));
-    });
-
+    }
     closeQuestionnaireModal();
-  };
+  }, [closeQuestionnaireModal, currentUser?._id, questionnaireOrder._id, t]);
 
   const questionnaireOrderTitle = lang === 'en_US' ? questionnaireOrder.title.en_US : questionnaireOrder.title.ja_JP;
 
@@ -166,7 +171,7 @@ const QuestionnaireModal = ({ questionnaireOrder, growiQuestionnaireServerOrigin
             </div>
           </div>
           {questionnaireOrder.questions?.map((question) => {
-            return <Question question={question} inputNamePrefix={inputNamePrefix} key={question._id.toString()}/>;
+            return <Question question={question} inputNamePrefix={inputNamePrefix} key={question._id}/>;
           })}
         </div>
       </ModalBody>
@@ -174,7 +179,7 @@ const QuestionnaireModal = ({ questionnaireOrder, growiQuestionnaireServerOrigin
         {currentUser?.admin
         && <a href="" className="mr-auto d-flex align-items-center"><i className="material-icons mr-1">settings</i>{t('questionnaire.settings')}</a>}
         <>
-          <button type="button" className="btn btn-outline-secondary mr-3" onClick={skipHandler}>{t('questionnaire.dont_show_again')}</button>
+          <button type="button" className="btn btn-outline-secondary mr-3" onClick={skipBtnClickHandler}>{t('questionnaire.dont_show_again')}</button>
           <button type="submit" className="btn btn-primary">{t('questionnaire.answer')}</button>
         </>
       </ModalFooter>

+ 19 - 1
packages/app/src/components/Questionnaire/QuestionnaireToast.tsx

@@ -2,10 +2,14 @@ import { useState } from 'react';
 
 import { useTranslation } from 'next-i18next';
 
+import { apiv3Put } from '~/client/util/apiv3-client';
+import { toastError, toastSuccess } from '~/client/util/toastr';
 import { IQuestionnaireOrderHasId } from '~/interfaces/questionnaire/questionnaire-order';
 import { useCurrentUser } from '~/stores/context';
 import { useQuestionnaireModal } from '~/stores/modal';
+import loggerFactory from '~/utils/logger';
 
+const logger = loggerFactory('growi:QuestionnaireToast');
 
 type QuestionnaireToastProps = {
   questionnaireOrder: IQuestionnaireOrderHasId,
@@ -25,6 +29,20 @@ const QuestionnaireToast = ({ questionnaireOrder }: QuestionnaireToastProps): JS
     openQuestionnaireModal(questionnaireOrder._id);
   };
 
+  const skipBtnClickHandler = async() => {
+    apiv3Put('/questionnaire/skip', {
+      user: currentUser?._id,
+      questionnaireOrderId: questionnaireOrder._id,
+    }).then(() => {
+      toastSuccess(t('questionnaire.skipped'));
+    }).catch((e) => {
+      logger.error(e);
+      toastError(t('questionnaire.failed_to_update_answer_status'));
+    });
+
+    setIsOpen(false);
+  };
+
   const questionnaireOrderTitle = lang === 'en_US' ? questionnaireOrder.title.en_US : questionnaireOrder.title.ja_JP;
 
   return <div className={`toast ${isOpen ? 'show' : 'hide'}`}>
@@ -36,7 +54,7 @@ const QuestionnaireToast = ({ questionnaireOrder }: QuestionnaireToastProps): JS
     </div>
     <div className="toast-body bg-light d-flex justify-content-end">
       <button type="button" className="btn btn-secondary mr-3" onClick={answerBtnClickHandler}>{t('questionnaire.answer')}</button>
-      <button type="button" className="btn btn-secondary" onClick={() => setIsOpen(false)}>{t('questionnaire.skip')}</button>
+      <button type="button" className="btn btn-secondary" onClick={skipBtnClickHandler}>{t('questionnaire.skip')}</button>
     </div>
   </div>;
 };

+ 2 - 4
packages/app/src/interfaces/questionnaire/answer.ts

@@ -1,6 +1,4 @@
-import { Types } from 'mongoose';
-
-export interface IAnswer {
-  question: Types.ObjectId | string
+export interface IAnswer<ID = string> {
+  question: ID
   value: string
 }

+ 2 - 2
packages/app/src/pages/[[...path]].page.tsx

@@ -7,7 +7,7 @@ import {
   isClient, isIPageInfoForEntity, pagePathUtils, pathUtils,
 } from '@growi/core';
 import type {
-  IDataWithMeta, IPageInfoForEntity, IPagePopulatedToShowRevision, IUser, IUserHasId,
+  IDataWithMeta, IPageInfoForEntity, IPagePopulatedToShowRevision, IUserHasId,
 } from '@growi/core';
 import ExtensibleCustomError from 'extensible-custom-error';
 import {
@@ -160,7 +160,7 @@ const PutbackPageModal = (): JSX.Element => {
 };
 
 type Props = CommonProps & {
-  currentUser: IUser,
+  currentUser: IUserHasId,
 
   pageWithMeta: IPageToShowRevisionWithMeta | null,
   // pageUser?: any,

+ 2 - 2
packages/app/src/pages/_private-legacy-pages.page.tsx

@@ -10,7 +10,7 @@ import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
-import type { IUser, IUserHasId } from '~/interfaces/user';
+import type { IUserHasId } from '~/interfaces/user';
 import type { IUserUISettings } from '~/interfaces/user-ui-settings';
 import type { UserUISettingsModel } from '~/server/models/user-ui-settings';
 import {
@@ -29,7 +29,7 @@ import {
 const SearchResultLayout = dynamic(() => import('~/components/Layout/SearchResultLayout'), { ssr: false });
 
 type Props = CommonProps & {
-  currentUser: IUser,
+  currentUser: IUserHasId,
 
   isSearchServiceConfigured: boolean,
   isSearchServiceReachable: boolean,

+ 3 - 3
packages/app/src/pages/_search.page.tsx

@@ -1,17 +1,17 @@
 import {
   NextPage, GetServerSideProps, GetServerSidePropsContext,
 } from 'next';
+import { useTranslation } from 'next-i18next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
 
-import { useTranslation } from 'next-i18next';
 import SearchResultLayout from '~/components/Layout/SearchResultLayout';
 import { DrawioViewerScript } from '~/components/Script/DrawioViewerScript';
 import type { CrowiRequest } from '~/interfaces/crowi-request';
 import type { RendererConfig } from '~/interfaces/services/renderer';
 import type { ISidebarConfig } from '~/interfaces/sidebar-config';
-import type { IUser, IUserHasId } from '~/interfaces/user';
+import type { IUserHasId } from '~/interfaces/user';
 import type { IUserUISettings } from '~/interfaces/user-ui-settings';
 import type { UserUISettingsModel } from '~/server/models/user-ui-settings';
 import {
@@ -32,7 +32,7 @@ import {
 
 
 type Props = CommonProps & {
-  currentUser: IUser,
+  currentUser: IUserHasId,
 
   isSearchServiceConfigured: boolean,
   isSearchServiceReachable: boolean,

+ 2 - 2
packages/app/src/pages/invited.page.tsx

@@ -1,6 +1,6 @@
 import React from 'react';
 
-import type { IUserHasId, IUser } from '@growi/core';
+import type { IUserHasId } from '@growi/core';
 import { USER_STATUS } from '@growi/core';
 import { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
 import { useTranslation } from 'next-i18next';
@@ -21,7 +21,7 @@ import {
 const InvitedForm = dynamic<InvitedFormProps>(() => import('~/components/InvitedForm').then(mod => mod.InvitedForm), { ssr: false });
 
 type Props = CommonProps & {
-  currentUser: IUser,
+  currentUser: IUserHasId,
   invitedFormUsername: string,
   invitedFormName: string,
 }

+ 2 - 2
packages/app/src/pages/maintenance.page.tsx

@@ -1,4 +1,4 @@
-import type { IUser, IUserHasId } from '@growi/core';
+import type { IUserHasId } from '@growi/core';
 import { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
 import { useTranslation } from 'next-i18next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
@@ -14,7 +14,7 @@ import {
 
 
 type Props = CommonProps & {
-  currentUser: IUser,
+  currentUser: IUserHasId,
 };
 
 const MaintenancePage: NextPage<CommonProps> = (props: Props) => {

+ 2 - 2
packages/app/src/pages/tags.page.tsx

@@ -1,6 +1,6 @@
 import React, { useState, useCallback } from 'react';
 
-import type { IUser, IUserHasId } from '@growi/core';
+import type { IUserHasId } from '@growi/core';
 import { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
 import { useTranslation } from 'next-i18next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
@@ -33,7 +33,7 @@ import {
 const PAGING_LIMIT = 10;
 
 type Props = CommonProps & {
-  currentUser: IUser,
+  currentUser: IUserHasId,
   isSearchServiceConfigured: boolean,
   isSearchServiceReachable: boolean,
   isSearchScopeChildrenAsDefault: boolean,

+ 3 - 3
packages/app/src/pages/trash.page.tsx

@@ -1,7 +1,8 @@
 import React from 'react';
 
-import type { IUser, IUserHasId } from '@growi/core';
+import type { IUserHasId } from '@growi/core';
 import { NextPage, GetServerSideProps, GetServerSidePropsContext } from 'next';
+import { useTranslation } from 'next-i18next';
 import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
 import dynamic from 'next/dynamic';
 import Head from 'next/head';
@@ -28,14 +29,13 @@ import { NextPageWithLayout } from './_app.page';
 import {
   CommonProps, getServerSideCommonProps, getNextI18NextConfig, generateCustomTitleForPage,
 } from './utils/commons';
-import { useTranslation } from 'next-i18next';
 
 const TrashPageList = dynamic(() => import('~/components/TrashPageList').then(mod => mod.TrashPageList), { ssr: false });
 const EmptyTrashModal = dynamic(() => import('~/components/EmptyTrashModal'), { ssr: false });
 const PutbackPageModal = dynamic(() => import('~/components/PutbackPageModal'), { ssr: false });
 
 type Props = CommonProps & {
-  currentUser: IUser,
+  currentUser: IUserHasId,
   isSearchServiceConfigured: boolean,
   isSearchServiceReachable: boolean,
   isSearchScopeChildrenAsDefault: boolean,

+ 2 - 2
packages/app/src/pages/utils/commons.ts

@@ -1,4 +1,4 @@
-import type { ColorScheme, IUser, IUserHasId } from '@growi/core';
+import type { ColorScheme, IUserHasId } from '@growi/core';
 import {
   DevidedPagePath, Lang, AllLang,
 } from '@growi/core';
@@ -22,7 +22,7 @@ export type CommonProps = {
   isMaintenanceMode: boolean,
   redirectDestination: string | null,
   isDefaultLogo: boolean,
-  currentUser?: IUser,
+  currentUser?: IUserHasId,
   forcedColorScheme?: ColorScheme,
 } & Partial<SSRConfig>;
 

+ 3 - 3
packages/app/src/server/routes/apiv3/questionnaire.ts

@@ -1,4 +1,4 @@
-import express, { Request } from 'express';
+import { Request, Router } from 'express';
 
 import { StatusType } from '~/interfaces/questionnaire/questionnaire-answer-status';
 import QuestionnaireAnswerStatus from '~/server/models/questionnaire/questionnaire-answer-status';
@@ -9,7 +9,7 @@ import { ApiV3Response } from './interfaces/apiv3-response';
 
 const logger = loggerFactory('growi:routes:apiv3:questionnaire');
 
-const router = express.Router();
+const router = Router();
 
 const changeAnswerStatus = async(user, questionnaireOrderId, status: StatusType): Promise<number> => {
   const result = await QuestionnaireAnswerStatus.updateOne({
@@ -30,7 +30,7 @@ const changeAnswerStatus = async(user, questionnaireOrderId, status: StatusType)
 
 module.exports = () => {
 
-  router.get('/questionnaire-orders', async(req: Request, res: ApiV3Response) => {
+  router.get('/orders', async(req: Request, res: ApiV3Response) => {
     const currentDate = new Date();
     try {
       const questionnaireOrders = await QuestionnaireOrder.find({

+ 3 - 3
packages/app/src/stores/context.tsx

@@ -1,4 +1,4 @@
-import type { ColorScheme, IUser } from '@growi/core';
+import type { ColorScheme, IUser, IUserHasId } from '@growi/core';
 import { Key, SWRResponse } from 'swr';
 import useSWRImmutable from 'swr/immutable';
 
@@ -36,8 +36,8 @@ export const useConfidential = (initialData?: string): SWRResponse<string, Error
   return useContextSWR('confidential', initialData);
 };
 
-export const useCurrentUser = (initialData?: Nullable<IUser>): SWRResponse<Nullable<IUser>, Error> => {
-  return useContextSWR<Nullable<IUser>, Error>('currentUser', initialData);
+export const useCurrentUser = (initialData?: Nullable<IUserHasId>): SWRResponse<Nullable<IUserHasId>, Error> => {
+  return useContextSWR<Nullable<IUserHasId>, Error>('currentUser', initialData);
 };
 
 export const useCurrentPathname = (initialData?: string): SWRResponse<string, Error> => {

+ 1 - 1
packages/app/src/stores/questionnaire.tsx

@@ -5,7 +5,7 @@ import { IQuestionnaireOrderHasId } from '~/interfaces/questionnaire/questionnai
 
 export const useSWRxQuestionnaireOrders = (): SWRResponse<IQuestionnaireOrderHasId[], Error> => {
   return useSWR(
-    '/questionnaire/questionnaire-orders',
+    '/questionnaire/orders',
     endpoint => apiv3Get(endpoint).then((response) => {
       return response.data.questionnaireOrders;
     }),