From 8d21f23658cccbade8d0e294a2794ffd2e6ba461 Mon Sep 17 00:00:00 2001 From: Yen-chi Chen Date: Fri, 6 Dec 2024 23:32:17 +0800 Subject: [PATCH 1/2] =?UTF-8?q?chore:=20=E4=BF=AE=E4=B8=80=E4=BA=9B=20cons?= =?UTF-8?q?tants=20=E6=88=96=E6=98=AF=E5=8F=83=E6=95=B8=20(#1430)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/CompanyAndJobTitle/IndexPage/index.js | 3 +-- src/components/CompanyAndJobTitle/Overview/Overview.js | 6 +++--- src/components/CompanyAndJobTitle/Overview/index.js | 4 +--- .../CompanyAndJobTitle/TimeAndSalary/index.js | 7 +++---- src/constants/companyJobTitle.js | 2 ++ src/pages/Company/CompanyIndexProvider.js | 7 ++----- .../Company/CompanyInterviewExperiencesProvider.js | 10 ++++++---- src/pages/Company/CompanyOverviewProvider.js | 10 +++++----- src/pages/Company/CompanyTimeAndSalaryProvider.js | 10 ++++++---- src/pages/Company/CompanyWorkExperiencesProvider.js | 10 ++++++---- src/pages/JobTitle/JobTitleIndexProvider.js | 7 ++----- .../JobTitle/JobTitleInterviewExperiencesProvider.js | 10 ++++++---- src/pages/JobTitle/JobTitleOverviewProvider.js | 10 +++++----- src/pages/JobTitle/JobTitleTimeAndSalaryProvider.js | 10 ++++++---- src/pages/JobTitle/JobTitleWorkExperiencesProvider.js | 10 ++++++---- 15 files changed, 60 insertions(+), 56 deletions(-) diff --git a/src/components/CompanyAndJobTitle/IndexPage/index.js b/src/components/CompanyAndJobTitle/IndexPage/index.js index d549f568f..1c4566ce1 100644 --- a/src/components/CompanyAndJobTitle/IndexPage/index.js +++ b/src/components/CompanyAndJobTitle/IndexPage/index.js @@ -9,14 +9,13 @@ import { pageTypeTranslation, generatePageURL, generateIndexURL, + PAGE_SIZE, } from 'constants/companyJobTitle'; import styles from './CompanyAndJobTitleIndex.module.css'; import { isFetched } from 'utils/fetchBox'; import { formatTitle, formatCanonicalPath } from 'utils/helmetHelper'; import { SITE_NAME } from 'constants/helmetData'; -const PAGE_SIZE = 10; - const IndexHelmet = ({ pageType, page }) => { const title = `所有${pageTypeTranslation[pageType]}資料 - 第${page}頁`; diff --git a/src/components/CompanyAndJobTitle/Overview/Overview.js b/src/components/CompanyAndJobTitle/Overview/Overview.js index fefe33a38..bcfa79d76 100644 --- a/src/components/CompanyAndJobTitle/Overview/Overview.js +++ b/src/components/CompanyAndJobTitle/Overview/Overview.js @@ -100,16 +100,16 @@ const Overview = ({ Overview.propTypes = { averageWeekWorkTime: PropTypes.number.isRequired, - interviewExperiences: PropTypes.arrayOf(PropTypes.object), + interviewExperiences: PropTypes.arrayOf(PropTypes.object).isRequired, interviewExperiencesCount: PropTypes.number.isRequired, jobAverageSalaries: PropTypes.array, overtimeFrequencyCount: PropTypes.object.isRequired, pageName: PropTypes.string.isRequired, pageType: PropTypes.string.isRequired, salaryDistribution: PropTypes.array, - salaryWorkTimes: PropTypes.arrayOf(PropTypes.object), + salaryWorkTimes: PropTypes.arrayOf(PropTypes.object).isRequired, salaryWorkTimesCount: PropTypes.number.isRequired, - workExperiences: PropTypes.arrayOf(PropTypes.object), + workExperiences: PropTypes.arrayOf(PropTypes.object).isRequired, workExperiencesCount: PropTypes.number.isRequired, }; diff --git a/src/components/CompanyAndJobTitle/Overview/index.js b/src/components/CompanyAndJobTitle/Overview/index.js index c2f65c981..89eea7f43 100644 --- a/src/components/CompanyAndJobTitle/Overview/index.js +++ b/src/components/CompanyAndJobTitle/Overview/index.js @@ -5,7 +5,7 @@ import { BoxStatusRenderer } from '../StatusRenderer'; import OverviewSection from './Overview'; import Helmet from './Helmet'; -const Overview = ({ pageType, pageName, tabType, overviewBox, page }) => ( +const Overview = ({ pageType, pageName, tabType, overviewBox }) => ( ( jobAverageSalaries={data.jobAverageSalaries} averageWeekWorkTime={data.averageWeekWorkTime} overtimeFrequencyCount={data.overtimeFrequencyCount} - page={page} /> ); @@ -66,7 +65,6 @@ Overview.propTypes = { error: PropTypes.any, status: PropTypes.string.isRequired, }).isRequired, - page: PropTypes.number.isRequired, pageName: PropTypes.string.isRequired, pageType: PropTypes.string.isRequired, tabType: PropTypes.string.isRequired, diff --git a/src/components/CompanyAndJobTitle/TimeAndSalary/index.js b/src/components/CompanyAndJobTitle/TimeAndSalary/index.js index b72179ad2..6682ba8ef 100644 --- a/src/components/CompanyAndJobTitle/TimeAndSalary/index.js +++ b/src/components/CompanyAndJobTitle/TimeAndSalary/index.js @@ -43,7 +43,6 @@ const TimeAndSalary = ({ pageName={pageName} tabType={tabType} salaryWorkTimes={salaryWorkTimes} - salaryWorkTimeStatistics={salaryWorkTimeStatistics} page={page} pageSize={pageSize} totalCount={totalCount} @@ -57,12 +56,12 @@ const TimeAndSalary = ({ TimeAndSalary.propTypes = { page: PropTypes.number.isRequired, - pageName: PropTypes.string, + pageName: PropTypes.string.isRequired, pageSize: PropTypes.number.isRequired, - pageType: PropTypes.string, + pageType: PropTypes.string.isRequired, salaryWorkTimeStatistics: PropTypes.object.isRequired, salaryWorkTimes: PropTypes.array, - tabType: PropTypes.string, + tabType: PropTypes.string.isRequired, totalCount: PropTypes.number.isRequired, }; diff --git a/src/constants/companyJobTitle.js b/src/constants/companyJobTitle.js index 8b0156ff3..6ec2a90b4 100644 --- a/src/constants/companyJobTitle.js +++ b/src/constants/companyJobTitle.js @@ -61,3 +61,5 @@ export const generateIndexURL = ({ pageType }) => generatePath('/:pageTypeURL', { pageTypeURL: pageTypeURLMap[pageType], }); + +export const PAGE_SIZE = 10; diff --git a/src/pages/Company/CompanyIndexProvider.js b/src/pages/Company/CompanyIndexProvider.js index d256cb92a..8bbadc648 100644 --- a/src/pages/Company/CompanyIndexProvider.js +++ b/src/pages/Company/CompanyIndexProvider.js @@ -4,15 +4,13 @@ import { querySelector } from 'common/routing/selectors'; import { pageFromQuerySelector } from 'selectors/routing/page'; import CompanyAndJobTitleIndexPage from 'components/CompanyAndJobTitle/IndexPage'; import usePagination from 'components/CompanyAndJobTitle/IndexPage/usePagination'; -import { pageType } from 'constants/companyJobTitle'; +import { pageType as PAGE_TYPE, PAGE_SIZE } from 'constants/companyJobTitle'; import { fetchCompanyNames } from 'actions/company'; import { companyIndexesBoxSelectorAtPage, companiesCountSelector, } from 'selectors/companyAndJobTitle'; -const PAGE_SIZE = 10; - const CompanyIndexProvider = () => { const [page, getPageLink] = usePagination(); const selector = useMemo(() => companyIndexesBoxSelectorAtPage(page), [page]); @@ -27,8 +25,7 @@ const CompanyIndexProvider = () => { return ( { return useSelector(selector); }; -const PAGE_SIZE = 10; - const CompanyInterviewExperiencesProvider = () => { const dispatch = useDispatch(); const pageType = PAGE_TYPE.COMPANY; @@ -83,7 +85,7 @@ const CompanyInterviewExperiencesProvider = () => { page={page} pageSize={PAGE_SIZE} totalCount={interviewExperiencesCount} - tabType={tabType.INTERVIEW_EXPERIENCE} + tabType={TAB_TYPE.INTERVIEW_EXPERIENCE} status={status} interviewExperiences={interviewExperiences} /> diff --git a/src/pages/Company/CompanyOverviewProvider.js b/src/pages/Company/CompanyOverviewProvider.js index f9a1602f2..74f458ae5 100644 --- a/src/pages/Company/CompanyOverviewProvider.js +++ b/src/pages/Company/CompanyOverviewProvider.js @@ -2,8 +2,10 @@ import React, { useCallback, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import Overview from 'components/CompanyAndJobTitle/Overview'; import usePermission from 'hooks/usePermission'; -import { usePage } from 'hooks/routing/page'; -import { tabType, pageType as PAGE_TYPE } from 'constants/companyJobTitle'; +import { + tabType as TAB_TYPE, + pageType as PAGE_TYPE, +} from 'constants/companyJobTitle'; import { queryCompanyOverview, queryRatingStatistics } from 'actions/company'; import { jobAverageSalaries, @@ -42,7 +44,6 @@ const CompanyOverviewProvider = () => { const dispatch = useDispatch(); const pageType = PAGE_TYPE.COMPANY; const pageName = usePageName(); - const page = usePage(); useEffect(() => { dispatch(queryRatingStatistics(pageName)); @@ -63,9 +64,8 @@ const CompanyOverviewProvider = () => { ); }; diff --git a/src/pages/Company/CompanyTimeAndSalaryProvider.js b/src/pages/Company/CompanyTimeAndSalaryProvider.js index 79c2ba068..b5ed50747 100644 --- a/src/pages/Company/CompanyTimeAndSalaryProvider.js +++ b/src/pages/Company/CompanyTimeAndSalaryProvider.js @@ -3,7 +3,11 @@ import { useSelector, useDispatch } from 'react-redux'; import TimeAndSalary from 'components/CompanyAndJobTitle/TimeAndSalary'; import usePermission from 'hooks/usePermission'; import { usePage } from 'hooks/routing/page'; -import { tabType, pageType as PAGE_TYPE } from 'constants/companyJobTitle'; +import { + tabType as TAB_TYPE, + pageType as PAGE_TYPE, + PAGE_SIZE, +} from 'constants/companyJobTitle'; import { queryCompanyTimeAndSalary, queryCompanyTimeAndSalaryStatistics, @@ -52,8 +56,6 @@ const useTimeAndSalaryBox = pageName => { return useSelector(selector); }; -const PAGE_SIZE = 10; - const CompanyTimeAndSalaryProvider = () => { const dispatch = useDispatch(); const pageType = PAGE_TYPE.COMPANY; @@ -104,7 +106,7 @@ const CompanyTimeAndSalaryProvider = () => { page={page} pageSize={PAGE_SIZE} totalCount={salaryWorkTimesCount} - tabType={tabType.TIME_AND_SALARY} + tabType={TAB_TYPE.TIME_AND_SALARY} status={status} salaryWorkTimes={salaryWorkTimes} salaryWorkTimeStatistics={salaryWorkTimeStatistics} diff --git a/src/pages/Company/CompanyWorkExperiencesProvider.js b/src/pages/Company/CompanyWorkExperiencesProvider.js index 6c5ab0fea..6ee09b9f2 100644 --- a/src/pages/Company/CompanyWorkExperiencesProvider.js +++ b/src/pages/Company/CompanyWorkExperiencesProvider.js @@ -3,7 +3,11 @@ import { useSelector, useDispatch } from 'react-redux'; import WorkExperiences from 'components/CompanyAndJobTitle/WorkExperiences'; import usePermission from 'hooks/usePermission'; import { usePage } from 'hooks/routing/page'; -import { tabType, pageType as PAGE_TYPE } from 'constants/companyJobTitle'; +import { + tabType as TAB_TYPE, + pageType as PAGE_TYPE, + PAGE_SIZE, +} from 'constants/companyJobTitle'; import { queryCompanyWorkExperiences, queryRatingStatistics, @@ -38,8 +42,6 @@ const useWorkExperiencesBox = pageName => { return useSelector(selector); }; -const PAGE_SIZE = 10; - const CompanyWorkExperiencesProvider = () => { const dispatch = useDispatch(); const pageType = PAGE_TYPE.COMPANY; @@ -82,7 +84,7 @@ const CompanyWorkExperiencesProvider = () => { page={page} pageSize={PAGE_SIZE} totalCount={workExperiencesCount} - tabType={tabType.WORK_EXPERIENCE} + tabType={TAB_TYPE.WORK_EXPERIENCE} status={status} workExperiences={workExperiences} /> diff --git a/src/pages/JobTitle/JobTitleIndexProvider.js b/src/pages/JobTitle/JobTitleIndexProvider.js index e8905acf4..3939e8dfb 100644 --- a/src/pages/JobTitle/JobTitleIndexProvider.js +++ b/src/pages/JobTitle/JobTitleIndexProvider.js @@ -4,15 +4,13 @@ import { querySelector } from 'common/routing/selectors'; import { pageFromQuerySelector } from 'selectors/routing/page'; import CompanyAndJobTitleIndexPage from 'components/CompanyAndJobTitle/IndexPage'; import usePagination from 'components/CompanyAndJobTitle/IndexPage/usePagination'; -import { pageType } from 'constants/companyJobTitle'; +import { pageType as PAGE_TYPE, PAGE_SIZE } from 'constants/companyJobTitle'; import { fetchJobTitles } from 'actions/jobTitle'; import { jobTitleIndexesBoxSelectorAtPage, jobTitlesCountSelector, } from 'selectors/companyAndJobTitle'; -const PAGE_SIZE = 10; - const JobTitleIndexProvider = () => { const [page, getPageLink] = usePagination(); const selector = useMemo(() => jobTitleIndexesBoxSelectorAtPage(page), [ @@ -29,8 +27,7 @@ const JobTitleIndexProvider = () => { return ( { return useSelector(selector); }; -const PAGE_SIZE = 10; - const JobTitleTimeAndSalaryProvider = () => { const dispatch = useDispatch(); const pageType = PAGE_TYPE.JOB_TITLE; @@ -77,7 +79,7 @@ const JobTitleTimeAndSalaryProvider = () => { page={page} pageSize={PAGE_SIZE} totalCount={interviewExperiencesCount} - tabType={tabType.INTERVIEW_EXPERIENCE} + tabType={TAB_TYPE.INTERVIEW_EXPERIENCE} status={status} interviewExperiences={interviewExperiences} /> diff --git a/src/pages/JobTitle/JobTitleOverviewProvider.js b/src/pages/JobTitle/JobTitleOverviewProvider.js index 8b9d8b8b9..bc30ad415 100644 --- a/src/pages/JobTitle/JobTitleOverviewProvider.js +++ b/src/pages/JobTitle/JobTitleOverviewProvider.js @@ -2,8 +2,10 @@ import React, { useCallback, useEffect } from 'react'; import { useSelector, useDispatch } from 'react-redux'; import Overview from 'components/CompanyAndJobTitle/Overview'; import usePermission from 'hooks/usePermission'; -import { usePage } from 'hooks/routing/page'; -import { tabType, pageType as PAGE_TYPE } from 'constants/companyJobTitle'; +import { + tabType as TAB_TYPE, + pageType as PAGE_TYPE, +} from 'constants/companyJobTitle'; import { queryJobTitleOverview } from 'actions/jobTitle'; import { salaryDistribution, @@ -42,7 +44,6 @@ const JobTitleOverviewProvider = () => { const dispatch = useDispatch(); const pageType = PAGE_TYPE.JOB_TITLE; const pageName = usePageName(); - const page = usePage(); useEffect(() => { dispatch(queryJobTitleOverview(pageName)); @@ -59,9 +60,8 @@ const JobTitleOverviewProvider = () => { ); }; diff --git a/src/pages/JobTitle/JobTitleTimeAndSalaryProvider.js b/src/pages/JobTitle/JobTitleTimeAndSalaryProvider.js index 433032c9c..179ae7829 100644 --- a/src/pages/JobTitle/JobTitleTimeAndSalaryProvider.js +++ b/src/pages/JobTitle/JobTitleTimeAndSalaryProvider.js @@ -3,7 +3,11 @@ import { useSelector, useDispatch } from 'react-redux'; import TimeAndSalary from 'components/CompanyAndJobTitle/TimeAndSalary'; import usePermission from 'hooks/usePermission'; import { usePage } from 'hooks/routing/page'; -import { tabType, pageType as PAGE_TYPE } from 'constants/companyJobTitle'; +import { + tabType as TAB_TYPE, + pageType as PAGE_TYPE, + PAGE_SIZE, +} from 'constants/companyJobTitle'; import { queryJobTitleTimeAndSalary, queryJobTitleTimeAndSalaryStatistics, @@ -54,8 +58,6 @@ const useTimeAndSalaryBox = pageName => { return useSelector(selector); }; -const PAGE_SIZE = 10; - const JobTitleTimeAndSalaryProvider = () => { const dispatch = useDispatch(); const pageType = PAGE_TYPE.JOB_TITLE; @@ -102,7 +104,7 @@ const JobTitleTimeAndSalaryProvider = () => { page={page} pageSize={PAGE_SIZE} totalCount={salaryWorkTimesCount} - tabType={tabType.TIME_AND_SALARY} + tabType={TAB_TYPE.TIME_AND_SALARY} status={status} salaryWorkTimes={salaryWorkTimes} salaryWorkTimeStatistics={salaryWorkTimeStatistics} diff --git a/src/pages/JobTitle/JobTitleWorkExperiencesProvider.js b/src/pages/JobTitle/JobTitleWorkExperiencesProvider.js index 17b720449..b22e0b658 100644 --- a/src/pages/JobTitle/JobTitleWorkExperiencesProvider.js +++ b/src/pages/JobTitle/JobTitleWorkExperiencesProvider.js @@ -3,7 +3,11 @@ import { useSelector, useDispatch } from 'react-redux'; import WorkExperiences from 'components/CompanyAndJobTitle/WorkExperiences'; import usePermission from 'hooks/usePermission'; import { usePage } from 'hooks/routing/page'; -import { tabType, pageType as PAGE_TYPE } from 'constants/companyJobTitle'; +import { + tabType as TAB_TYPE, + pageType as PAGE_TYPE, + PAGE_SIZE, +} from 'constants/companyJobTitle'; import { queryJobTitleWorkExperiences } from 'actions/jobTitle'; import { workExperiences as workExperiencesSelector, @@ -35,8 +39,6 @@ const useWorkExperiencesBox = pageName => { return useSelector(selector); }; -const PAGE_SIZE = 10; - const JobTitleWorkExperiencesProvider = () => { const dispatch = useDispatch(); const pageType = PAGE_TYPE.JOB_TITLE; @@ -75,7 +77,7 @@ const JobTitleWorkExperiencesProvider = () => { page={page} pageSize={PAGE_SIZE} totalCount={workExperiencesCount} - tabType={tabType.WORK_EXPERIENCE} + tabType={TAB_TYPE.WORK_EXPERIENCE} status={status} workExperiences={workExperiences} /> From 9eaa1373ba58d4d8b317df3f7f7c37f22736bf83 Mon Sep 17 00:00:00 2001 From: Peter Shih Date: Mon, 9 Dec 2024 10:16:21 +0800 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20=E5=9C=A8=E9=9D=A2=E8=A9=A6?= =?UTF-8?q?=E8=A1=A8=E5=96=AE=E6=94=BE=20error=20log=20(#1456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/actions/auth.js | 76 ++++++++----------- src/actions/toastNotification.js | 51 +++++++++++++ .../InterviewForm/TypeForm/index.js | 4 +- .../TimeSalaryForm/TypeForm.js | 4 +- .../WorkExperiencesForm/TypeForm.js | 4 +- .../common/SubmittableFormBuilder.js | 4 +- src/components/common/FormBuilder/index.js | 18 ++++- src/constants/errorCodeMsg.js | 62 ++++++++++----- 8 files changed, 147 insertions(+), 76 deletions(-) diff --git a/src/actions/auth.js b/src/actions/auth.js index b867dc761..95095eba0 100644 --- a/src/actions/auth.js +++ b/src/actions/auth.js @@ -5,11 +5,23 @@ import { } from 'apis/auth'; import { queryMeApi } from 'apis/me'; import authStatus from 'constants/authStatus'; -import rollbar from 'utils/rollbar'; -import { NOTIFICATION_TYPE } from 'constants/toastNotification'; -import { ERROR_CODE_MSG } from 'constants/errorCodeMsg'; -import { pushNotification } from 'actions/toastNotification'; +import { pushErrorNotificationAndRollbarAndThrowError } from 'actions/toastNotification'; import { GraphqlError } from 'utils/errors'; +import { + ER0001, + ER0002, + ER0003, + ER0004, + ER0006, + ER0009, + ER0010, + ER0011, + ER0012, + ER0013, + ER0014, + ER0015, + ER0016, +} from 'constants/errorCodeMsg'; export const SET_LOGIN = '@@auth/SET_LOGIN'; export const SET_USER = '@@auth/SET_USER'; @@ -42,40 +54,12 @@ const FBSDKLogin = FB => { }); }; -const composeErrMsg = (code, message, error) => { - if (error) { - return `[${code}] ${message}: ${error}`; - } else { - return `[${code}] ${message}`; - } -}; -const loginErrorToast = (code, message) => - pushNotification(NOTIFICATION_TYPE.ALERT, composeErrMsg(code, message)); - -const toastNotificationAndRollbarAndThrowError = (errorCode, error, extra) => ( - dispatch, - getState, -) => { - dispatch(loginErrorToast(errorCode, ERROR_CODE_MSG[errorCode].external)); - const internalMsg = composeErrMsg( - errorCode, - ERROR_CODE_MSG[errorCode].internal, - error, - ); - if (!extra) { - rollbar.error(internalMsg); - } else { - rollbar.error(internalMsg, extra); - } - throw new Error(internalMsg); -}; - /** * Use `hooks/login/useFacebookLogin` as possible */ export const loginWithFB = FBSDK => async (dispatch, getState) => { if (!FBSDK) { - dispatch(toastNotificationAndRollbarAndThrowError('ER0001')); + dispatch(pushErrorNotificationAndRollbarAndThrowError(ER0001)); } let fbLoginResponse = null; @@ -83,11 +67,11 @@ export const loginWithFB = FBSDK => async (dispatch, getState) => { // invoke FB SDK Login to get FB-issued access token fbLoginResponse = await FBSDKLogin(FBSDK); } catch (error) { - dispatch(toastNotificationAndRollbarAndThrowError('ER0002', error)); + dispatch(pushErrorNotificationAndRollbarAndThrowError(ER0002, error)); } if (!fbLoginResponse || !fbLoginResponse.status) { - dispatch(toastNotificationAndRollbarAndThrowError('ER0003')); + dispatch(pushErrorNotificationAndRollbarAndThrowError(ER0003)); } switch (fbLoginResponse.status) { @@ -95,7 +79,7 @@ export const loginWithFB = FBSDK => async (dispatch, getState) => { return; case authStatus.NOT_AUTHORIZED: dispatch(setLogin(authStatus.NOT_AUTHORIZED)); - dispatch(toastNotificationAndRollbarAndThrowError('ER0004')); + dispatch(pushErrorNotificationAndRollbarAndThrowError(ER0004)); break; case authStatus.CONNECTED: try { @@ -107,20 +91,20 @@ export const loginWithFB = FBSDK => async (dispatch, getState) => { } catch (error) { if (error instanceof GraphqlError && error.codes) { if (error.codes[0] === 'UNAUTHENTICATED') { - dispatch(toastNotificationAndRollbarAndThrowError('ER0014')); + dispatch(pushErrorNotificationAndRollbarAndThrowError(ER0014)); break; } else if (error.codes[0] === 'FORBIDDEN') { - dispatch(toastNotificationAndRollbarAndThrowError('ER0015')); + dispatch(pushErrorNotificationAndRollbarAndThrowError(ER0015)); break; } } - dispatch(toastNotificationAndRollbarAndThrowError('ER0016', error)); + dispatch(pushErrorNotificationAndRollbarAndThrowError(ER0016, error)); } break; default: dispatch( - toastNotificationAndRollbarAndThrowError( - 'ER0006', + pushErrorNotificationAndRollbarAndThrowError( + ER0006, null, fbLoginResponse, ), @@ -141,7 +125,7 @@ export const loginWithGoogle = credentialResponse => async ( ) => { // TODO: 當登入失敗 if (!credentialResponse || !credentialResponse.credential) { - dispatch(toastNotificationAndRollbarAndThrowError('ER0009')); + dispatch(pushErrorNotificationAndRollbarAndThrowError(ER0009)); } const idToken = credentialResponse.credential; try { @@ -149,19 +133,19 @@ export const loginWithGoogle = credentialResponse => async ( if (response && response.token) { await dispatch(loginWithToken(response.token)); } else { - dispatch(toastNotificationAndRollbarAndThrowError('ER0010')); + dispatch(pushErrorNotificationAndRollbarAndThrowError(ER0010)); } } catch (error) { if (error instanceof GraphqlError && error.codes) { if (error.codes[0] === 'UNAUTHENTICATED') { - dispatch(toastNotificationAndRollbarAndThrowError('ER0011')); + dispatch(pushErrorNotificationAndRollbarAndThrowError(ER0011)); return; } else if (error.codes[0] === 'FORBIDDEN') { - dispatch(toastNotificationAndRollbarAndThrowError('ER0012')); + dispatch(pushErrorNotificationAndRollbarAndThrowError(ER0012)); return; } } - dispatch(toastNotificationAndRollbarAndThrowError('ER0013', error)); + dispatch(pushErrorNotificationAndRollbarAndThrowError(ER0013, error)); } }; diff --git a/src/actions/toastNotification.js b/src/actions/toastNotification.js index 41e05b51c..bcd578c1a 100644 --- a/src/actions/toastNotification.js +++ b/src/actions/toastNotification.js @@ -1,3 +1,6 @@ +import rollbar from 'utils/rollbar'; +import { NOTIFICATION_TYPE } from 'constants/toastNotification'; +import { ERROR_CODE_MSG } from 'constants/errorCodeMsg'; import { generateNotification } from 'utils/toastNotification'; export const PUSH = '@@TOAST_NOTIFICATION/PUSH'; @@ -16,3 +19,51 @@ export const removeNotification = id => ({ type: REMOVE, id, }); + +const composeErrorMessage = (code, message, error) => { + if (error) { + return `[${code}] ${message}: ${error}`; + } else { + return `[${code}] ${message}`; + } +}; + +const pushErrorNotification = (code, message) => + pushNotification(NOTIFICATION_TYPE.ALERT, composeErrorMessage(code, message)); + +export const pushErrorNotificationAndRollbar = (errorCode, error, extra) => ( + dispatch, + getState, +) => { + dispatch( + pushErrorNotification(errorCode, ERROR_CODE_MSG[errorCode].external), + ); + + const internalMessage = composeErrorMessage( + errorCode, + ERROR_CODE_MSG[errorCode].internal, + error, + ); + + if (!extra) { + rollbar.error(internalMessage); + } else { + rollbar.error(internalMessage, extra); + } +}; + +export const pushErrorNotificationAndRollbarAndThrowError = ( + errorCode, + error, + extra, +) => (dispatch, getState) => { + dispatch(pushErrorNotificationAndRollbar(errorCode, error, extra)); + + const internalMessage = composeErrorMessage( + errorCode, + ERROR_CODE_MSG[errorCode].internal, + error, + ); + + throw new Error(internalMessage); +}; diff --git a/src/components/ShareExperience/InterviewForm/TypeForm/index.js b/src/components/ShareExperience/InterviewForm/TypeForm/index.js index acb0bd68e..a84973c6f 100644 --- a/src/components/ShareExperience/InterviewForm/TypeForm/index.js +++ b/src/components/ShareExperience/InterviewForm/TypeForm/index.js @@ -9,7 +9,7 @@ import Header, { CompanyJobTitleHeader } from '../../common/TypeFormHeader'; import SubmittableFormBuilder from '../../common/SubmittableFormBuilder'; import { createInterviewExperience } from 'actions/experiences'; import { GA_CATEGORY, GA_ACTION } from 'constants/gaConstants'; -import { ERROR_CODE_MSG } from 'constants/errorCodeMsg'; +import { ER0008, ERROR_CODE_MSG } from 'constants/errorCodeMsg'; import { DATA_KEY_COMPANY_NAME, DATA_KEY_JOB_TITLE, @@ -165,7 +165,7 @@ const TypeForm = ({ open, onClose }) => { category: GA_CATEGORY.SHARE_INTERVIEW_TYPE_FORM, action: GA_ACTION.UPLOAD_FAIL, }); - const errorCode = 'ER0008'; + const errorCode = ER0008; rollbar.error( `[${errorCode}] ${ERROR_CODE_MSG[errorCode].internal} ${error.message}`, error, diff --git a/src/components/ShareExperience/TimeSalaryForm/TypeForm.js b/src/components/ShareExperience/TimeSalaryForm/TypeForm.js index f69683631..4c502b717 100644 --- a/src/components/ShareExperience/TimeSalaryForm/TypeForm.js +++ b/src/components/ShareExperience/TimeSalaryForm/TypeForm.js @@ -39,7 +39,7 @@ import { DATA_KEY_HAS_OVERTIME_SALARY, DATA_KEY_HAS_COMPENSATORY_DAYOFF, } from '../constants'; -import { ERROR_CODE_MSG } from 'constants/errorCodeMsg'; +import { ER0007, ERROR_CODE_MSG } from 'constants/errorCodeMsg'; import { evolve } from '../utils'; import { generateTabURL, pageType, tabType } from 'constants/companyJobTitle'; @@ -167,7 +167,7 @@ const TypeForm = ({ open, onClose, hideProgressBar = false }) => { : GA_CATEGORY.SHARE_TIME_SALARY_TYPE_FORM, action: GA_ACTION.UPLOAD_FAIL, }); - const errorCode = 'ER0007'; + const errorCode = ER0007; rollbar.error( `[${errorCode}] ${ERROR_CODE_MSG[errorCode].internal} ${error.message}`, error, diff --git a/src/components/ShareExperience/WorkExperiencesForm/TypeForm.js b/src/components/ShareExperience/WorkExperiencesForm/TypeForm.js index 786f4e219..7c8c47879 100644 --- a/src/components/ShareExperience/WorkExperiencesForm/TypeForm.js +++ b/src/components/ShareExperience/WorkExperiencesForm/TypeForm.js @@ -37,7 +37,7 @@ import { tabType } from '../../../constants/companyJobTitle'; import { createWorkExperienceWithRating } from 'actions/experiences'; import { transferKeyToSnakecase } from 'utils/objectUtil'; import { GA_CATEGORY, GA_ACTION } from 'constants/gaConstants'; -import { ERROR_CODE_MSG } from 'constants/errorCodeMsg'; +import { ER0020, ERROR_CODE_MSG } from 'constants/errorCodeMsg'; import { sendEvent } from 'utils/hotjarUtil'; import rollbar from 'utils/rollbar'; @@ -139,7 +139,7 @@ const TypeForm = ({ open, onClose, hideProgressBar = false }) => { category: GA_CATEGORY.SHARE_WORK, action: GA_ACTION.UPLOAD_FAIL, }); - const errorCode = 'ER0020'; + const errorCode = ER0020; rollbar.error( `[${errorCode}] ${ERROR_CODE_MSG[errorCode].internal} ${error.message}`, error, diff --git a/src/components/ShareExperience/common/SubmittableFormBuilder.js b/src/components/ShareExperience/common/SubmittableFormBuilder.js index 4ceeabfe2..bd5fd59c5 100644 --- a/src/components/ShareExperience/common/SubmittableFormBuilder.js +++ b/src/components/ShareExperience/common/SubmittableFormBuilder.js @@ -10,7 +10,7 @@ import ConfirmModal from 'common/FormBuilder/Modals/ConfirmModal'; import Footer from './TypeFormFooter'; import { useExperienceCount, useSalaryWorkTimeCount } from 'hooks/useCount'; import rollbar from 'utils/rollbar'; -import { ERROR_CODE_MSG } from 'constants/errorCodeMsg'; +import { ER0018, ERROR_CODE_MSG } from 'constants/errorCodeMsg'; const SubmittableTypeForm = ({ open, @@ -38,7 +38,7 @@ const SubmittableTypeForm = ({ setSubmittedDraft(draft); setSubmitStatus('success'); } catch (error) { - const errorCode = 'ER0018'; + const errorCode = ER0018; rollbar.error( `[${errorCode}] ${ERROR_CODE_MSG[errorCode].internal} ${error}`, error, diff --git a/src/components/common/FormBuilder/index.js b/src/components/common/FormBuilder/index.js index cc82de211..7e1f34457 100644 --- a/src/components/common/FormBuilder/index.js +++ b/src/components/common/FormBuilder/index.js @@ -24,7 +24,9 @@ import AnimatedPager from './AnimatedPager'; import styles from './FormBuilder.module.css'; import { OptionPropType } from './QuestionBuilder/Checkbox/PropTypes'; import rollbar from 'utils/rollbar'; -import { ERROR_CODE_MSG } from 'constants/errorCodeMsg'; +import { ER0019, ER0021, ERROR_CODE_MSG } from 'constants/errorCodeMsg'; +import { useDispatch } from 'react-redux'; +import { pushErrorNotificationAndRollbar } from 'actions/toastNotification'; const findIfQuestionsAcceptDraft = draft => R.all( @@ -110,6 +112,7 @@ const FormBuilder = ({ const [isWarningShown, setWarningShown] = useState(false); const [showsNavigation, setShowsNavigation] = useState(true); + const dispatch = useDispatch(); const isSubmittable = useMemo( () => findIfQuestionsAcceptDraft(draft)(questions), @@ -120,10 +123,11 @@ const FormBuilder = ({ if (warning) { if (onValidateFail) onValidateFail({ dataKey, value: draft[dataKey], warning }); + else dispatch(pushErrorNotificationAndRollbar(ER0021)); } else if (isSubmittable) { onSubmit(draft); } else { - const errorCode = 'ER0019'; + const errorCode = ER0019; rollbar.error( `[${errorCode}] ${ERROR_CODE_MSG[errorCode].internal}`, null, @@ -131,7 +135,15 @@ const FormBuilder = ({ ); console.error(`Not submittable`); } - }, [warning, isSubmittable, onValidateFail, dataKey, draft, onSubmit]); + }, [ + warning, + isSubmittable, + onValidateFail, + dataKey, + draft, + onSubmit, + dispatch, + ]); useEffect(() => { if (open) { diff --git a/src/constants/errorCodeMsg.js b/src/constants/errorCodeMsg.js index a8d8fb302..d66a8ddf5 100644 --- a/src/constants/errorCodeMsg.js +++ b/src/constants/errorCodeMsg.js @@ -4,81 +4,105 @@ const LOGIN_ERROR_MSG = const LOGIN_ERROR_MSG_CLEAN_BROWSER_DATA = '登入時發生錯誤,請清除瀏覽器資料,再重試一次。若錯誤持續發生,請聯繫 findyourgoodjob@gmail.com'; +export const ER0001 = 'ER0001'; +export const ER0002 = 'ER0002'; +export const ER0003 = 'ER0003'; +export const ER0004 = 'ER0004'; +export const ER0005 = 'ER0005'; +export const ER0006 = 'ER0006'; +export const ER0007 = 'ER0007'; +export const ER0008 = 'ER0008'; +export const ER0009 = 'ER0009'; +export const ER0010 = 'ER0010'; +export const ER0011 = 'ER0011'; +export const ER0012 = 'ER0012'; +export const ER0013 = 'ER0013'; +export const ER0014 = 'ER0014'; +export const ER0015 = 'ER0015'; +export const ER0016 = 'ER0016'; +export const ER0018 = 'ER0018'; +export const ER0019 = 'ER0019'; +export const ER0020 = 'ER0020'; +export const ER0021 = 'ER0021'; + // maintain a list of global error code export const ERROR_CODE_MSG = { - ER0001: { + [ER0001]: { external: LOGIN_ERROR_MSG, internal: 'FB SDK is not ready', }, - ER0002: { + [ER0002]: { external: LOGIN_ERROR_MSG, internal: 'FB SDK login failed', }, - ER0003: { + [ER0003]: { external: LOGIN_ERROR_MSG, internal: 'FB login response is empty or does not have status field', }, - ER0004: { + [ER0004]: { external: LOGIN_ERROR_MSG, internal: 'FB login failed: unauthorized', }, - ER0005: { + [ER0005]: { external: LOGIN_ERROR_MSG, internal: 'Graphql mutation facebookLogin failed', }, - ER0006: { + [ER0006]: { external: LOGIN_ERROR_MSG_CLEAN_BROWSER_DATA, internal: 'FB login failed: unknown auth status', }, - ER0007: { + [ER0007]: { internal: 'Submit salary failed', }, - ER0008: { + [ER0008]: { internal: 'Submit interview failed', }, - ER0009: { + [ER0009]: { external: LOGIN_ERROR_MSG, internal: 'Cannot get id_token from Google auth API', }, - ER0010: { + [ER0010]: { external: LOGIN_ERROR_MSG, internal: 'Cannot get token from Graphql googleLogin mutation response', }, - ER0011: { + [ER0011]: { external: LOGIN_ERROR_MSG, internal: 'Graphql googleLogin mutation API failed with codes=UNAUTHENTICATED', }, - ER0012: { + [ER0012]: { external: LOGIN_ERROR_MSG, internal: 'Graphql googleLogin mutation API failed with codes=FORBIDDEN, probably this user is deactivated', }, - ER0013: { + [ER0013]: { external: LOGIN_ERROR_MSG, internal: 'Unknown error during Graphql googleLogin mutation', }, - ER0014: { + [ER0014]: { external: LOGIN_ERROR_MSG, internal: 'Graphql facebookLogin mutation API failed with codes=UNAUTHENTICATED', }, - ER0015: { + [ER0015]: { external: LOGIN_ERROR_MSG, internal: 'Graphql facebookLogin mutation API failed with codes=FORBIDDEN, probably this user is deactivated', }, - ER0016: { + [ER0016]: { external: LOGIN_ERROR_MSG, internal: 'Unknown error during Graphql facebookLogin mutation', }, - ER0018: { + [ER0018]: { internal: 'Unexpected error during submitting form data', }, - ER0019: { + [ER0019]: { internal: 'Not submittable', }, - ER0020: { + [ER0020]: { internal: 'Submit work experience failed', }, + [ER0021]: { + internal: 'Warning without validation failure', + }, };