From 9c825da286ac326e7c0bc55d5832dd55d9e89a92 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Mon, 4 Nov 2024 10:44:53 +0700 Subject: [PATCH 01/16] Add task specific max length validation --- src/CONST.ts | 11 ++++++ src/components/ExceededCommentLength.tsx | 8 +++-- .../ReportActionCompose.tsx | 34 +++++++++++++++---- src/pages/home/report/ReportFooter.tsx | 13 +------ 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 437ee4e7fd42..b1f4f4f681fa 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -275,6 +275,8 @@ type OnboardingMessageType = { type?: string; }; +const EMAIL_WITH_OPTIONAL_DOMAIN = /(?=((?=[\w'#%+-]+(?:\.[\w'#%+-]+)*@?)[\w.'#%+-]{1,64}(?:@(?:(?=[a-z\d]+(?:-+[a-z\d]+)*\.)(?:[a-z\d-]{1,63}\.)+[a-z]{2,63}))?(?= |_|\b))(?.*))\S{3,254}(?=\k$)/; + const CONST = { HEIC_SIGNATURES: [ '6674797068656963', // 'ftypheic' - Indicates standard HEIC file @@ -2971,6 +2973,15 @@ const CONST = { get EXPENSIFY_POLICY_DOMAIN_NAME() { return new RegExp(`${EXPENSIFY_POLICY_DOMAIN}([a-zA-Z0-9]+)\\${EXPENSIFY_POLICY_DOMAIN_EXTENSION}`); }, + + /** + * Matching task rule by group + * Group 1: Start task rule with [] + * Group 2: Optional email group between \s+....\s* start rule with @+valid email or short mention + * Group 3: Title is remaining characters + */ + // The regex is copied from the expensify-common CONST file, but the domain is optional to accept short mention + TASK_TITLE_WITH_OPTONAL_SHORT_MENTION: `^\\[\\]\\s+(?:@(?:${EMAIL_WITH_OPTIONAL_DOMAIN}))?\\s*([\\s\\S]*)` }, PRONOUNS: { diff --git a/src/components/ExceededCommentLength.tsx b/src/components/ExceededCommentLength.tsx index 2f0887afc8f1..a67558e1b6e0 100644 --- a/src/components/ExceededCommentLength.tsx +++ b/src/components/ExceededCommentLength.tsx @@ -4,7 +4,11 @@ import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import Text from './Text'; -function ExceededCommentLength() { +type ExceededCommentLengthProps = { + shouldUseTitleLimit?: boolean; +}; + +function ExceededCommentLength({shouldUseTitleLimit}: ExceededCommentLengthProps) { const styles = useThemeStyles(); const {numberFormat, translate} = useLocalize(); @@ -13,7 +17,7 @@ function ExceededCommentLength() { style={[styles.textMicro, styles.textDanger, styles.chatItemComposeSecondaryRow, styles.mlAuto, styles.pl2]} numberOfLines={1} > - {translate('composer.commentExceededMaxLength', {formattedMaxLength: numberFormat(CONST.MAX_COMMENT_LENGTH)})} + {translate('composer.commentExceededMaxLength', { formattedMaxLength: numberFormat(shouldUseTitleLimit ? CONST.TITLE_CHARACTER_LIMIT : CONST.MAX_COMMENT_LENGTH) })} ); } diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 14908014ca03..024c15051d3b 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -172,6 +172,7 @@ function ReportActionCompose({ * Shows red borders and prevents the comment from being sent */ const {hasExceededMaxCommentLength, validateCommentMaxLength} = useHandleExceedMaxCommentLength(); + const [hasExceededTaskTitleLength, setHasExceededTaskTitleLength] = useState(false); const suggestionsRef = useRef(null); const composerRef = useRef(); @@ -332,7 +333,7 @@ function ReportActionCompose({ const hasReportRecipient = !isEmptyObject(reportRecipient); - const isSendDisabled = isCommentEmpty || isBlockedFromConcierge || !!disabled || hasExceededMaxCommentLength; + const isSendDisabled = isCommentEmpty || isBlockedFromConcierge || !!disabled || hasExceededMaxCommentLength || hasExceededTaskTitleLength; // Note: using JS refs is not well supported in reanimated, thus we need to store the function in a shared value // useSharedValue on web doesn't support functions, so we need to wrap it in an object. @@ -393,6 +394,18 @@ function ReportActionCompose({ ], ); + const debouncedValidate = useDebounce((value, reportID) => { + const match = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); + if (!match) { + setHasExceededTaskTitleLength(false); + validateCommentMaxLength(value, { reportID }); + return; + } + + let title = match[3] ? match[3].trim().replace(/\n/g, ' ') : undefined; + setHasExceededTaskTitleLength(title ? title.length > CONST.TITLE_CHARACTER_LIMIT : false); + }, 100); + return ( @@ -425,7 +438,7 @@ function ReportActionCompose({ styles.flexRow, styles.chatItemComposeBox, isComposerFullSize && styles.chatItemFullComposeBox, - hasExceededMaxCommentLength && styles.borderColorDanger, + (hasExceededMaxCommentLength || hasExceededTaskTitleLength) && styles.borderColorDanger, ]} > setIsAttachmentPreviewActive(true)} onModalHide={onAttachmentPreviewClose} - shouldDisableSendButton={hasExceededMaxCommentLength} + shouldDisableSendButton={hasExceededMaxCommentLength || hasExceededTaskTitleLength} > {({displayFileInModal}) => ( <> @@ -453,7 +466,7 @@ function ReportActionCompose({ onAddActionPressed={onAddActionPressed} onItemSelected={onItemSelected} actionButtonRef={actionButtonRef} - shouldDisableAttachmentItem={hasExceededMaxCommentLength} + shouldDisableAttachmentItem={hasExceededMaxCommentLength || hasExceededTaskTitleLength} /> { @@ -494,7 +507,16 @@ function ReportActionCompose({ if (value.length === 0 && isComposerFullSize) { Report.setIsComposerFullSize(reportID, false); } - validateCommentMaxLength(value, {reportID}); + + debouncedValidate(value, reportID); + // const match = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); + // if (!match) { + // validateCommentMaxLength(value, {reportID}); + // return; + // } + + // let title = match[3] ? match[3].trim().replace(/\n/g, ' ') : undefined; + // setHasExceededTaskTitleLength(title ? title.length > CONST.TITLE_CHARACTER_LIMIT : false); }} /> {!shouldUseNarrowLayout && } - {hasExceededMaxCommentLength && } + {(hasExceededMaxCommentLength || hasExceededTaskTitleLength) && } {!isSmallScreenWidth && ( diff --git a/src/pages/home/report/ReportFooter.tsx b/src/pages/home/report/ReportFooter.tsx index c087510374be..ec03393161f3 100644 --- a/src/pages/home/report/ReportFooter.tsx +++ b/src/pages/home/report/ReportFooter.tsx @@ -124,18 +124,7 @@ function ReportFooter({ const handleCreateTask = useCallback( (text: string): boolean => { - /** - * Matching task rule by group - * Group 1: Start task rule with [] - * Group 2: Optional email group between \s+....\s* start rule with @+valid email or short mention - * Group 3: Title is remaining characters - */ - // The regex is copied from the expensify-common CONST file, but the domain is optional to accept short mention - const emailWithOptionalDomainRegex = - /(?=((?=[\w'#%+-]+(?:\.[\w'#%+-]+)*@?)[\w.'#%+-]{1,64}(?:@(?:(?=[a-z\d]+(?:-+[a-z\d]+)*\.)(?:[a-z\d-]{1,63}\.)+[a-z]{2,63}))?(?= |_|\b))(?.*))\S{3,254}(?=\k$)/; - const taskRegex = `^\\[\\]\\s+(?:@(?:${emailWithOptionalDomainRegex.source}))?\\s*([\\s\\S]*)`; - - const match = text.match(taskRegex); + const match = text.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); if (!match) { return false; } From bd3a70e15a53618d05c1dc6ba28d571ec710ad10 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Mon, 4 Nov 2024 10:48:24 +0700 Subject: [PATCH 02/16] remove unnecessary comment --- .../report/ReportActionCompose/ReportActionCompose.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 024c15051d3b..e745836b1b9e 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -509,14 +509,6 @@ function ReportActionCompose({ } debouncedValidate(value, reportID); - // const match = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); - // if (!match) { - // validateCommentMaxLength(value, {reportID}); - // return; - // } - - // let title = match[3] ? match[3].trim().replace(/\n/g, ' ') : undefined; - // setHasExceededTaskTitleLength(title ? title.length > CONST.TITLE_CHARACTER_LIMIT : false); }} /> Date: Thu, 21 Nov 2024 10:16:18 +0700 Subject: [PATCH 03/16] put most logic to useHandleExceedMaxCommentLength --- src/components/ExceededCommentLength.tsx | 6 +-- src/hooks/useHandleExceedMaxCommentLength.ts | 28 ++++++++++--- .../ReportActionCompose.tsx | 41 +++++++------------ 3 files changed, 40 insertions(+), 35 deletions(-) diff --git a/src/components/ExceededCommentLength.tsx b/src/components/ExceededCommentLength.tsx index a67558e1b6e0..21a133e4416c 100644 --- a/src/components/ExceededCommentLength.tsx +++ b/src/components/ExceededCommentLength.tsx @@ -5,10 +5,10 @@ import CONST from '@src/CONST'; import Text from './Text'; type ExceededCommentLengthProps = { - shouldUseTitleLimit?: boolean; + maxCommentLength?: number; }; -function ExceededCommentLength({shouldUseTitleLimit}: ExceededCommentLengthProps) { +function ExceededCommentLength({maxCommentLength = CONST.MAX_COMMENT_LENGTH}: ExceededCommentLengthProps) { const styles = useThemeStyles(); const {numberFormat, translate} = useLocalize(); @@ -17,7 +17,7 @@ function ExceededCommentLength({shouldUseTitleLimit}: ExceededCommentLengthProps style={[styles.textMicro, styles.textDanger, styles.chatItemComposeSecondaryRow, styles.mlAuto, styles.pl2]} numberOfLines={1} > - {translate('composer.commentExceededMaxLength', { formattedMaxLength: numberFormat(shouldUseTitleLimit ? CONST.TITLE_CHARACTER_LIMIT : CONST.MAX_COMMENT_LENGTH) })} + {translate('composer.commentExceededMaxLength', { formattedMaxLength: numberFormat(maxCommentLength) })} ); } diff --git a/src/hooks/useHandleExceedMaxCommentLength.ts b/src/hooks/useHandleExceedMaxCommentLength.ts index 8afa88da38b5..d7db6b038739 100644 --- a/src/hooks/useHandleExceedMaxCommentLength.ts +++ b/src/hooks/useHandleExceedMaxCommentLength.ts @@ -4,25 +4,43 @@ import * as ReportUtils from '@libs/ReportUtils'; import type {ParsingDetails} from '@libs/ReportUtils'; import CONST from '@src/CONST'; -const useHandleExceedMaxCommentLength = () => { +const useHandleExceedMaxCommentLength = (enableTaskTitleValidation : boolean = false) => { const [hasExceededMaxCommentLength, setHasExceededMaxCommentLength] = useState(false); + const [isTaskTitle, setIsTaskTitle] = useState(false); + const [maxCommentLength, setMaxCommentLength] = useState(isTaskTitle ? CONST.TITLE_CHARACTER_LIMIT : CONST.MAX_COMMENT_LENGTH); const handleValueChange = useCallback( (value: string, parsingDetails?: ParsingDetails) => { + if (enableTaskTitleValidation) { + const match = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); + if (match) { + const title = match[3] ? match[3].trim().replace(/\n/g, ' ') : undefined; + setHasExceededMaxCommentLength(title ? title.length > CONST.TITLE_CHARACTER_LIMIT : false); + setMaxCommentLength(CONST.TITLE_CHARACTER_LIMIT); + setIsTaskTitle(true); + return; + } + } + if (ReportUtils.getCommentLength(value, parsingDetails) <= CONST.MAX_COMMENT_LENGTH) { if (hasExceededMaxCommentLength) { setHasExceededMaxCommentLength(false); } + setMaxCommentLength(CONST.MAX_COMMENT_LENGTH); + setIsTaskTitle(false); return; } setHasExceededMaxCommentLength(true); + setMaxCommentLength(CONST.MAX_COMMENT_LENGTH); + setIsTaskTitle(false); }, - [hasExceededMaxCommentLength], + [hasExceededMaxCommentLength, enableTaskTitleValidation], ); - const validateCommentMaxLength = useMemo(() => debounce(handleValueChange, 1500, {leading: true}), [handleValueChange]); + // Use a shorter debounce time for task title validation to provide quicker feedback due to simpler logic + const validateCommentMaxLength = useMemo(() => debounce(handleValueChange, isTaskTitle ? 100 : 1500, { leading: true }), [handleValueChange]); - return {hasExceededMaxCommentLength, validateCommentMaxLength}; + return {hasExceededMaxCommentLength, validateCommentMaxLength, maxCommentLength}; }; -export default useHandleExceedMaxCommentLength; +export default useHandleExceedMaxCommentLength; \ No newline at end of file diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 52f7dfdf6c61..ac773a8ebee3 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -171,8 +171,7 @@ function ReportActionCompose({ * Updates the composer when the comment length is exceeded * Shows red borders and prevents the comment from being sent */ - const {hasExceededMaxCommentLength, validateCommentMaxLength} = useHandleExceedMaxCommentLength(); - const [hasExceededTaskTitleLength, setHasExceededTaskTitleLength] = useState(false); + const {hasExceededMaxCommentLength, validateCommentMaxLength, maxCommentLength} = useHandleExceedMaxCommentLength(true); const suggestionsRef = useRef(null); const composerRef = useRef(); @@ -334,7 +333,7 @@ function ReportActionCompose({ const hasReportRecipient = !isEmptyObject(reportRecipient); - const isSendDisabled = isCommentEmpty || isBlockedFromConcierge || !!disabled || hasExceededMaxCommentLength || hasExceededTaskTitleLength; + const isSendDisabled = isCommentEmpty || isBlockedFromConcierge || !!disabled || hasExceededMaxCommentLength; // Note: using JS refs is not well supported in reanimated, thus we need to store the function in a shared value // useSharedValue on web doesn't support functions, so we need to wrap it in an object. @@ -395,26 +394,14 @@ function ReportActionCompose({ ], ); - const onValueChange = useDebounce( - useCallback( - (value: string) => { - if (value.length === 0 && isComposerFullSize) { - Report.setIsComposerFullSize(reportID, false); - } - - const match = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); - if (!match) { - setHasExceededTaskTitleLength(false); - validateCommentMaxLength(value, { reportID }); - return; - } - - let title = match[3] ? match[3].trim().replace(/\n/g, ' ') : undefined; - setHasExceededTaskTitleLength(title ? title.length > CONST.TITLE_CHARACTER_LIMIT : false); - }, - [isComposerFullSize, reportID, validateCommentMaxLength], - ), - 100, + const onValueChange = useCallback( + (value: string) => { + if (value.length === 0 && isComposerFullSize) { + Report.setIsComposerFullSize(reportID, false); + } + validateCommentMaxLength(value, {reportID}); + }, + [isComposerFullSize, reportID, validateCommentMaxLength], ); return ( @@ -449,7 +436,7 @@ function ReportActionCompose({ styles.flexRow, styles.chatItemComposeBox, isComposerFullSize && styles.chatItemFullComposeBox, - (hasExceededMaxCommentLength || hasExceededTaskTitleLength) && styles.borderColorDanger, + hasExceededMaxCommentLength && styles.borderColorDanger, ]} > setIsAttachmentPreviewActive(true)} onModalHide={onAttachmentPreviewClose} - shouldDisableSendButton={hasExceededMaxCommentLength || hasExceededTaskTitleLength} + shouldDisableSendButton={hasExceededMaxCommentLength} > {({displayFileInModal}) => ( <> @@ -476,7 +463,7 @@ function ReportActionCompose({ onAddActionPressed={onAddActionPressed} onItemSelected={onItemSelected} actionButtonRef={actionButtonRef} - shouldDisableAttachmentItem={hasExceededMaxCommentLength || hasExceededTaskTitleLength} + shouldDisableAttachmentItem={hasExceededMaxCommentLength} /> { @@ -562,7 +549,7 @@ function ReportActionCompose({ > {!shouldUseNarrowLayout && } - {(hasExceededMaxCommentLength || hasExceededTaskTitleLength) && } + {hasExceededMaxCommentLength && } {!isSmallScreenWidth && ( From f90ce912ae65d4fa59fd447e42417bfd9eeb7395 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 09:59:19 +0700 Subject: [PATCH 04/16] Use separate hooks to validate max title length --- src/CONST.ts | 5 ++-- src/components/ExceededCommentLength.tsx | 2 +- src/hooks/useHandleExceedMaxCommentLength.ts | 28 ++++--------------- .../useHandleExceedMaxTaskTitleLength.ts | 25 +++++++++++++++++ .../ReportActionCompose.tsx | 26 ++++++++++++----- 5 files changed, 53 insertions(+), 33 deletions(-) create mode 100644 src/hooks/useHandleExceedMaxTaskTitleLength.ts diff --git a/src/CONST.ts b/src/CONST.ts index b6ca33ba62cd..0d920143867c 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -294,7 +294,8 @@ type OnboardingMessage = { type?: string; }; -const EMAIL_WITH_OPTIONAL_DOMAIN = /(?=((?=[\w'#%+-]+(?:\.[\w'#%+-]+)*@?)[\w.'#%+-]{1,64}(?:@(?:(?=[a-z\d]+(?:-+[a-z\d]+)*\.)(?:[a-z\d-]{1,63}\.)+[a-z]{2,63}))?(?= |_|\b))(?.*))\S{3,254}(?=\k$)/; +const EMAIL_WITH_OPTIONAL_DOMAIN = + /(?=((?=[\w'#%+-]+(?:\.[\w'#%+-]+)*@?)[\w.'#%+-]{1,64}(?:@(?:(?=[a-z\d]+(?:-+[a-z\d]+)*\.)(?:[a-z\d-]{1,63}\.)+[a-z]{2,63}))?(?= |_|\b))(?.*))\S{3,254}(?=\k$)/; const CONST = { HEIC_SIGNATURES: [ @@ -3046,7 +3047,7 @@ const CONST = { * Group 3: Title is remaining characters */ // The regex is copied from the expensify-common CONST file, but the domain is optional to accept short mention - TASK_TITLE_WITH_OPTONAL_SHORT_MENTION: `^\\[\\]\\s+(?:@(?:${EMAIL_WITH_OPTIONAL_DOMAIN}))?\\s*([\\s\\S]*)` + TASK_TITLE_WITH_OPTONAL_SHORT_MENTION: `^\\[\\]\\s+(?:@(?:${EMAIL_WITH_OPTIONAL_DOMAIN}))?\\s*([\\s\\S]*)`, }, PRONOUNS: { diff --git a/src/components/ExceededCommentLength.tsx b/src/components/ExceededCommentLength.tsx index 21a133e4416c..e3d9f9767a06 100644 --- a/src/components/ExceededCommentLength.tsx +++ b/src/components/ExceededCommentLength.tsx @@ -17,7 +17,7 @@ function ExceededCommentLength({maxCommentLength = CONST.MAX_COMMENT_LENGTH}: Ex style={[styles.textMicro, styles.textDanger, styles.chatItemComposeSecondaryRow, styles.mlAuto, styles.pl2]} numberOfLines={1} > - {translate('composer.commentExceededMaxLength', { formattedMaxLength: numberFormat(maxCommentLength) })} + {translate('composer.commentExceededMaxLength', {formattedMaxLength: numberFormat(maxCommentLength)})} ); } diff --git a/src/hooks/useHandleExceedMaxCommentLength.ts b/src/hooks/useHandleExceedMaxCommentLength.ts index d7db6b038739..8afa88da38b5 100644 --- a/src/hooks/useHandleExceedMaxCommentLength.ts +++ b/src/hooks/useHandleExceedMaxCommentLength.ts @@ -4,43 +4,25 @@ import * as ReportUtils from '@libs/ReportUtils'; import type {ParsingDetails} from '@libs/ReportUtils'; import CONST from '@src/CONST'; -const useHandleExceedMaxCommentLength = (enableTaskTitleValidation : boolean = false) => { +const useHandleExceedMaxCommentLength = () => { const [hasExceededMaxCommentLength, setHasExceededMaxCommentLength] = useState(false); - const [isTaskTitle, setIsTaskTitle] = useState(false); - const [maxCommentLength, setMaxCommentLength] = useState(isTaskTitle ? CONST.TITLE_CHARACTER_LIMIT : CONST.MAX_COMMENT_LENGTH); const handleValueChange = useCallback( (value: string, parsingDetails?: ParsingDetails) => { - if (enableTaskTitleValidation) { - const match = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); - if (match) { - const title = match[3] ? match[3].trim().replace(/\n/g, ' ') : undefined; - setHasExceededMaxCommentLength(title ? title.length > CONST.TITLE_CHARACTER_LIMIT : false); - setMaxCommentLength(CONST.TITLE_CHARACTER_LIMIT); - setIsTaskTitle(true); - return; - } - } - if (ReportUtils.getCommentLength(value, parsingDetails) <= CONST.MAX_COMMENT_LENGTH) { if (hasExceededMaxCommentLength) { setHasExceededMaxCommentLength(false); } - setMaxCommentLength(CONST.MAX_COMMENT_LENGTH); - setIsTaskTitle(false); return; } setHasExceededMaxCommentLength(true); - setMaxCommentLength(CONST.MAX_COMMENT_LENGTH); - setIsTaskTitle(false); }, - [hasExceededMaxCommentLength, enableTaskTitleValidation], + [hasExceededMaxCommentLength], ); - // Use a shorter debounce time for task title validation to provide quicker feedback due to simpler logic - const validateCommentMaxLength = useMemo(() => debounce(handleValueChange, isTaskTitle ? 100 : 1500, { leading: true }), [handleValueChange]); + const validateCommentMaxLength = useMemo(() => debounce(handleValueChange, 1500, {leading: true}), [handleValueChange]); - return {hasExceededMaxCommentLength, validateCommentMaxLength, maxCommentLength}; + return {hasExceededMaxCommentLength, validateCommentMaxLength}; }; -export default useHandleExceedMaxCommentLength; \ No newline at end of file +export default useHandleExceedMaxCommentLength; diff --git a/src/hooks/useHandleExceedMaxTaskTitleLength.ts b/src/hooks/useHandleExceedMaxTaskTitleLength.ts new file mode 100644 index 000000000000..2e5966e360a5 --- /dev/null +++ b/src/hooks/useHandleExceedMaxTaskTitleLength.ts @@ -0,0 +1,25 @@ +import debounce from 'lodash/debounce'; +import {useCallback, useMemo, useState} from 'react'; +import CONST from '@src/CONST'; + +const useHandleExceedMaxTaskTitleLength = () => { + const [hasExceededMaxTitleLength, setHasExceededMaxTitleLength] = useState(false); + + const handleValueChange = useCallback((value: string) => { + const match = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); + if (match) { + const title = match[3] ? match[3].trim().replace(/\n/g, ' ') : undefined; + const exceeded = title ? title.length > CONST.TITLE_CHARACTER_LIMIT : false; + setHasExceededMaxTitleLength(exceeded); + return true; + } + setHasExceededMaxTitleLength(false); + return false; + }, []); + + const validateTitleMaxLength = useMemo(() => debounce(handleValueChange, 100, {leading: true}), [handleValueChange]); + + return {hasExceededMaxTitleLength, validateTitleMaxLength}; +}; + +export default useHandleExceedMaxTaskTitleLength; diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index ac773a8ebee3..871c8d6dfcb8 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -23,6 +23,7 @@ import EducationalTooltip from '@components/Tooltip/EducationalTooltip'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebounce from '@hooks/useDebounce'; import useHandleExceedMaxCommentLength from '@hooks/useHandleExceedMaxCommentLength'; +import useHandleExceedMaxTaskTitleLength from '@hooks/useHandleExceedMaxTaskTitleLength'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -171,7 +172,15 @@ function ReportActionCompose({ * Updates the composer when the comment length is exceeded * Shows red borders and prevents the comment from being sent */ - const {hasExceededMaxCommentLength, validateCommentMaxLength, maxCommentLength} = useHandleExceedMaxCommentLength(true); + const {hasExceededMaxCommentLength, validateCommentMaxLength} = useHandleExceedMaxCommentLength(); + const {hasExceededMaxTitleLength, validateTitleMaxLength} = useHandleExceedMaxTaskTitleLength(); + + let exceededMaxLength = null; + if (hasExceededMaxTitleLength) { + exceededMaxLength = CONST.TITLE_CHARACTER_LIMIT; + } else if (hasExceededMaxCommentLength) { + exceededMaxLength = CONST.MAX_COMMENT_LENGTH; + } const suggestionsRef = useRef(null); const composerRef = useRef(); @@ -333,7 +342,7 @@ function ReportActionCompose({ const hasReportRecipient = !isEmptyObject(reportRecipient); - const isSendDisabled = isCommentEmpty || isBlockedFromConcierge || !!disabled || hasExceededMaxCommentLength; + const isSendDisabled = isCommentEmpty || isBlockedFromConcierge || !!disabled || !!exceededMaxLength; // Note: using JS refs is not well supported in reanimated, thus we need to store the function in a shared value // useSharedValue on web doesn't support functions, so we need to wrap it in an object. @@ -399,9 +408,12 @@ function ReportActionCompose({ if (value.length === 0 && isComposerFullSize) { Report.setIsComposerFullSize(reportID, false); } + if (validateTitleMaxLength(value)) { + return; + } validateCommentMaxLength(value, {reportID}); }, - [isComposerFullSize, reportID, validateCommentMaxLength], + [isComposerFullSize, reportID, validateCommentMaxLength, validateTitleMaxLength], ); return ( @@ -436,7 +448,7 @@ function ReportActionCompose({ styles.flexRow, styles.chatItemComposeBox, isComposerFullSize && styles.chatItemFullComposeBox, - hasExceededMaxCommentLength && styles.borderColorDanger, + !!exceededMaxLength && styles.borderColorDanger, ]} > setIsAttachmentPreviewActive(true)} onModalHide={onAttachmentPreviewClose} - shouldDisableSendButton={hasExceededMaxCommentLength} + shouldDisableSendButton={!!exceededMaxLength} > {({displayFileInModal}) => ( <> @@ -463,7 +475,7 @@ function ReportActionCompose({ onAddActionPressed={onAddActionPressed} onItemSelected={onItemSelected} actionButtonRef={actionButtonRef} - shouldDisableAttachmentItem={hasExceededMaxCommentLength} + shouldDisableAttachmentItem={!!exceededMaxLength} /> { @@ -549,7 +561,7 @@ function ReportActionCompose({ > {!shouldUseNarrowLayout && } - {hasExceededMaxCommentLength && } + {!!exceededMaxLength && } {!isSmallScreenWidth && ( From a3bd4bff9125e10c2fb5cba578ebf99a1b10ec58 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 19:11:39 +0700 Subject: [PATCH 05/16] add hasExceededMaxTitleLength dependency --- src/hooks/useHandleExceedMaxTaskTitleLength.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useHandleExceedMaxTaskTitleLength.ts b/src/hooks/useHandleExceedMaxTaskTitleLength.ts index 2e5966e360a5..ff9fa5ac5e73 100644 --- a/src/hooks/useHandleExceedMaxTaskTitleLength.ts +++ b/src/hooks/useHandleExceedMaxTaskTitleLength.ts @@ -15,7 +15,7 @@ const useHandleExceedMaxTaskTitleLength = () => { } setHasExceededMaxTitleLength(false); return false; - }, []); + }, [hasExceededMaxTitleLength]); const validateTitleMaxLength = useMemo(() => debounce(handleValueChange, 100, {leading: true}), [handleValueChange]); From 3d40a79dab652f0f0d1299dc5eea30cae193e4e1 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 19:12:27 +0700 Subject: [PATCH 06/16] change debounce time to const --- src/hooks/useHandleExceedMaxTaskTitleLength.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hooks/useHandleExceedMaxTaskTitleLength.ts b/src/hooks/useHandleExceedMaxTaskTitleLength.ts index ff9fa5ac5e73..c2bf39811f24 100644 --- a/src/hooks/useHandleExceedMaxTaskTitleLength.ts +++ b/src/hooks/useHandleExceedMaxTaskTitleLength.ts @@ -17,7 +17,7 @@ const useHandleExceedMaxTaskTitleLength = () => { return false; }, [hasExceededMaxTitleLength]); - const validateTitleMaxLength = useMemo(() => debounce(handleValueChange, 100, {leading: true}), [handleValueChange]); + const validateTitleMaxLength = useMemo(() => debounce(handleValueChange, CONST.TIMING.COMMENT_LENGTH_DEBOUNCE_TIME, {leading: true}), [handleValueChange]); return {hasExceededMaxTitleLength, validateTitleMaxLength}; }; From dd65326e63ba4b1da536a713a67a0e39ef08b90a Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Fri, 22 Nov 2024 21:25:55 +0700 Subject: [PATCH 07/16] change exceededMaxLength to state --- .../useHandleExceedMaxTaskTitleLength.ts | 2 +- .../ReportActionCompose.tsx | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/hooks/useHandleExceedMaxTaskTitleLength.ts b/src/hooks/useHandleExceedMaxTaskTitleLength.ts index c2bf39811f24..249c72d7cd8b 100644 --- a/src/hooks/useHandleExceedMaxTaskTitleLength.ts +++ b/src/hooks/useHandleExceedMaxTaskTitleLength.ts @@ -15,7 +15,7 @@ const useHandleExceedMaxTaskTitleLength = () => { } setHasExceededMaxTitleLength(false); return false; - }, [hasExceededMaxTitleLength]); + }, []); const validateTitleMaxLength = useMemo(() => debounce(handleValueChange, CONST.TIMING.COMMENT_LENGTH_DEBOUNCE_TIME, {leading: true}), [handleValueChange]); diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 871c8d6dfcb8..f8bd6a810499 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -174,13 +174,7 @@ function ReportActionCompose({ */ const {hasExceededMaxCommentLength, validateCommentMaxLength} = useHandleExceedMaxCommentLength(); const {hasExceededMaxTitleLength, validateTitleMaxLength} = useHandleExceedMaxTaskTitleLength(); - - let exceededMaxLength = null; - if (hasExceededMaxTitleLength) { - exceededMaxLength = CONST.TITLE_CHARACTER_LIMIT; - } else if (hasExceededMaxCommentLength) { - exceededMaxLength = CONST.MAX_COMMENT_LENGTH; - } + const [exceededMaxLength, setExceededMaxLength] = useState(null); const suggestionsRef = useRef(null); const composerRef = useRef(); @@ -315,6 +309,18 @@ function ReportActionCompose({ onComposerFocus?.(); }, [onComposerFocus]); + useEffect(() => { + if (hasExceededMaxTitleLength) { + setExceededMaxLength(CONST.TITLE_CHARACTER_LIMIT); + return; + } + if (hasExceededMaxCommentLength) { + setExceededMaxLength(CONST.MAX_COMMENT_LENGTH); + return; + } + setExceededMaxLength(null); + }, [hasExceededMaxTitleLength, hasExceededMaxCommentLength]); + // We are returning a callback here as we want to incoke the method on unmount only useEffect( () => () => { From 2c3b86d03ab8361f9ed91c5e7446197f6a52c225 Mon Sep 17 00:00:00 2001 From: Wildan M Date: Sat, 23 Nov 2024 07:57:22 +0700 Subject: [PATCH 08/16] Update src/CONST.ts Co-authored-by: Marc Glasser --- src/CONST.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index e7021a93e47a..b7dbd91b0fff 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3046,7 +3046,6 @@ const CONST = { * Group 2: Optional email group between \s+....\s* start rule with @+valid email or short mention * Group 3: Title is remaining characters */ - // The regex is copied from the expensify-common CONST file, but the domain is optional to accept short mention TASK_TITLE_WITH_OPTONAL_SHORT_MENTION: `^\\[\\]\\s+(?:@(?:${EMAIL_WITH_OPTIONAL_DOMAIN}))?\\s*([\\s\\S]*)`, }, From 708adbf0451e0cd99b047b6a5f62f5382f7b6713 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Sat, 23 Nov 2024 08:13:52 +0700 Subject: [PATCH 09/16] Use separate text for task title validation message --- src/components/ExceededCommentLength.tsx | 8 +++++--- src/languages/en.ts | 1 + src/languages/es.ts | 1 + .../report/ReportActionCompose/ReportActionCompose.tsx | 7 ++++++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/components/ExceededCommentLength.tsx b/src/components/ExceededCommentLength.tsx index e3d9f9767a06..5c3d529fd871 100644 --- a/src/components/ExceededCommentLength.tsx +++ b/src/components/ExceededCommentLength.tsx @@ -6,22 +6,24 @@ import Text from './Text'; type ExceededCommentLengthProps = { maxCommentLength?: number; + isTaskTitle?: boolean; }; -function ExceededCommentLength({maxCommentLength = CONST.MAX_COMMENT_LENGTH}: ExceededCommentLengthProps) { +function ExceededCommentLength({maxCommentLength = CONST.MAX_COMMENT_LENGTH, isTaskTitle}: ExceededCommentLengthProps) { const styles = useThemeStyles(); const {numberFormat, translate} = useLocalize(); + const translationKey = isTaskTitle ? 'composer.taskTitleExceededMaxLength' : 'composer.commentExceededMaxLength'; + return ( - {translate('composer.commentExceededMaxLength', {formattedMaxLength: numberFormat(maxCommentLength)})} + {translate(translationKey, {formattedMaxLength: numberFormat(maxCommentLength)})} ); } - ExceededCommentLength.displayName = 'ExceededCommentLength'; export default memo(ExceededCommentLength); diff --git a/src/languages/en.ts b/src/languages/en.ts index 3e1ea7f0d7cc..775928142679 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -524,6 +524,7 @@ const translations = { noExtensionFoundForMimeType: 'No extension found for mime type', problemGettingImageYouPasted: 'There was a problem getting the image you pasted', commentExceededMaxLength: ({formattedMaxLength}: FormattedMaxLengthParams) => `The maximum comment length is ${formattedMaxLength} characters.`, + taskTitleExceededMaxLength: ({formattedMaxLength}: FormattedMaxLengthParams) => `The maximum task title length is ${formattedMaxLength} characters.`, }, baseUpdateAppModal: { updateApp: 'Update app', diff --git a/src/languages/es.ts b/src/languages/es.ts index d28a19fbb1be..a760d19ab0a3 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -515,6 +515,7 @@ const translations = { noExtensionFoundForMimeType: 'No se encontró una extension para este tipo de contenido', problemGettingImageYouPasted: 'Ha ocurrido un problema al obtener la imagen que has pegado', commentExceededMaxLength: ({formattedMaxLength}: FormattedMaxLengthParams) => `El comentario debe tener máximo ${formattedMaxLength} caracteres.`, + taskTitleExceededMaxLength: ({formattedMaxLength}: FormattedMaxLengthParams) => `La longitud máxima del título de una tarea es de ${formattedMaxLength} caracteres.`, }, baseUpdateAppModal: { updateApp: 'Actualizar app', diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index f8bd6a810499..719b5041abb0 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -567,7 +567,12 @@ function ReportActionCompose({ > {!shouldUseNarrowLayout && } - {!!exceededMaxLength && } + {!!exceededMaxLength && ( + + )} {!isSmallScreenWidth && ( From ed1b95d7773806bfa40bce100071a3fe68193a3f Mon Sep 17 00:00:00 2001 From: Wildan M Date: Tue, 26 Nov 2024 21:03:17 +0700 Subject: [PATCH 10/16] Update src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx Co-authored-by: Vinh Hoang --- .../home/report/ReportActionCompose/ReportActionCompose.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 719b5041abb0..c58c78ac16a6 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -173,7 +173,7 @@ function ReportActionCompose({ * Shows red borders and prevents the comment from being sent */ const {hasExceededMaxCommentLength, validateCommentMaxLength} = useHandleExceedMaxCommentLength(); - const {hasExceededMaxTitleLength, validateTitleMaxLength} = useHandleExceedMaxTaskTitleLength(); + const {hasExceededMaxTaskTitleLength, validateTaskTitleMaxLength} = useHandleExceedMaxTaskTitleLength(); const [exceededMaxLength, setExceededMaxLength] = useState(null); const suggestionsRef = useRef(null); From c3c07961e49d21cb5814424c6efef3f08384c6e4 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 26 Nov 2024 21:12:07 +0700 Subject: [PATCH 11/16] Refactor --- src/hooks/useHandleExceedMaxTaskTitleLength.ts | 6 +++--- .../report/ReportActionCompose/ReportActionCompose.tsx | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/hooks/useHandleExceedMaxTaskTitleLength.ts b/src/hooks/useHandleExceedMaxTaskTitleLength.ts index 249c72d7cd8b..ae0be4e798ed 100644 --- a/src/hooks/useHandleExceedMaxTaskTitleLength.ts +++ b/src/hooks/useHandleExceedMaxTaskTitleLength.ts @@ -3,7 +3,7 @@ import {useCallback, useMemo, useState} from 'react'; import CONST from '@src/CONST'; const useHandleExceedMaxTaskTitleLength = () => { - const [hasExceededMaxTitleLength, setHasExceededMaxTitleLength] = useState(false); + const [hasExceededMaxTaskTitleLength, setHasExceededMaxTitleLength] = useState(false); const handleValueChange = useCallback((value: string) => { const match = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); @@ -17,9 +17,9 @@ const useHandleExceedMaxTaskTitleLength = () => { return false; }, []); - const validateTitleMaxLength = useMemo(() => debounce(handleValueChange, CONST.TIMING.COMMENT_LENGTH_DEBOUNCE_TIME, {leading: true}), [handleValueChange]); + const validateTaskTitleMaxLength = useMemo(() => debounce(handleValueChange, CONST.TIMING.COMMENT_LENGTH_DEBOUNCE_TIME, {leading: true}), [handleValueChange]); - return {hasExceededMaxTitleLength, validateTitleMaxLength}; + return {hasExceededMaxTaskTitleLength, validateTaskTitleMaxLength}; }; export default useHandleExceedMaxTaskTitleLength; diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index c58c78ac16a6..333a8743c3f0 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -310,7 +310,7 @@ function ReportActionCompose({ }, [onComposerFocus]); useEffect(() => { - if (hasExceededMaxTitleLength) { + if (hasExceededMaxTaskTitleLength) { setExceededMaxLength(CONST.TITLE_CHARACTER_LIMIT); return; } @@ -319,7 +319,7 @@ function ReportActionCompose({ return; } setExceededMaxLength(null); - }, [hasExceededMaxTitleLength, hasExceededMaxCommentLength]); + }, [hasExceededMaxTaskTitleLength, hasExceededMaxCommentLength]); // We are returning a callback here as we want to incoke the method on unmount only useEffect( @@ -414,12 +414,12 @@ function ReportActionCompose({ if (value.length === 0 && isComposerFullSize) { Report.setIsComposerFullSize(reportID, false); } - if (validateTitleMaxLength(value)) { + if (validateTaskTitleMaxLength(value)) { return; } validateCommentMaxLength(value, {reportID}); }, - [isComposerFullSize, reportID, validateCommentMaxLength, validateTitleMaxLength], + [isComposerFullSize, reportID, validateCommentMaxLength, validateTaskTitleMaxLength], ); return ( @@ -570,7 +570,7 @@ function ReportActionCompose({ {!!exceededMaxLength && ( )} From 58bbcbe8788254279181bc5c3101af33be99cd5b Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 26 Nov 2024 21:53:36 +0700 Subject: [PATCH 12/16] refactor for better readability --- .../report/ReportActionCompose/ReportActionCompose.tsx | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 333a8743c3f0..107652cb72d2 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -312,13 +312,11 @@ function ReportActionCompose({ useEffect(() => { if (hasExceededMaxTaskTitleLength) { setExceededMaxLength(CONST.TITLE_CHARACTER_LIMIT); - return; - } - if (hasExceededMaxCommentLength) { + } else if (hasExceededMaxCommentLength) { setExceededMaxLength(CONST.MAX_COMMENT_LENGTH); - return; + } else { + setExceededMaxLength(null); } - setExceededMaxLength(null); }, [hasExceededMaxTaskTitleLength, hasExceededMaxCommentLength]); // We are returning a callback here as we want to incoke the method on unmount only From 41c3215114ea05d7306c4d22e98cecae40305573 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Wed, 27 Nov 2024 23:15:16 +0700 Subject: [PATCH 13/16] extract debounce, refactor --- src/CONST.ts | 2 +- src/hooks/useHandleExceedMaxCommentLength.ts | 7 ++--- .../useHandleExceedMaxTaskTitleLength.ts | 18 +++--------- .../ReportActionCompose.tsx | 28 ++++++++++++++----- .../report/ReportActionItemMessageEdit.tsx | 5 ++-- 5 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index 204fde4f631d..4aaa7736b6f9 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1315,7 +1315,7 @@ const CONST = { TEST_TOOLS_MODAL_THROTTLE_TIME: 800, TOOLTIP_SENSE: 1000, TRIE_INITIALIZATION: 'trie_initialization', - COMMENT_LENGTH_DEBOUNCE_TIME: 500, + COMMENT_LENGTH_DEBOUNCE_TIME: 1500, SEARCH_OPTION_LIST_DEBOUNCE_TIME: 300, RESIZE_DEBOUNCE_TIME: 100, UNREAD_UPDATE_DEBOUNCE_TIME: 300, diff --git a/src/hooks/useHandleExceedMaxCommentLength.ts b/src/hooks/useHandleExceedMaxCommentLength.ts index 8afa88da38b5..be6381a64ea4 100644 --- a/src/hooks/useHandleExceedMaxCommentLength.ts +++ b/src/hooks/useHandleExceedMaxCommentLength.ts @@ -1,5 +1,4 @@ -import debounce from 'lodash/debounce'; -import {useCallback, useMemo, useState} from 'react'; +import {useCallback, useState} from 'react'; import * as ReportUtils from '@libs/ReportUtils'; import type {ParsingDetails} from '@libs/ReportUtils'; import CONST from '@src/CONST'; @@ -7,7 +6,7 @@ import CONST from '@src/CONST'; const useHandleExceedMaxCommentLength = () => { const [hasExceededMaxCommentLength, setHasExceededMaxCommentLength] = useState(false); - const handleValueChange = useCallback( + const validateCommentMaxLength = useCallback( (value: string, parsingDetails?: ParsingDetails) => { if (ReportUtils.getCommentLength(value, parsingDetails) <= CONST.MAX_COMMENT_LENGTH) { if (hasExceededMaxCommentLength) { @@ -20,8 +19,6 @@ const useHandleExceedMaxCommentLength = () => { [hasExceededMaxCommentLength], ); - const validateCommentMaxLength = useMemo(() => debounce(handleValueChange, 1500, {leading: true}), [handleValueChange]); - return {hasExceededMaxCommentLength, validateCommentMaxLength}; }; diff --git a/src/hooks/useHandleExceedMaxTaskTitleLength.ts b/src/hooks/useHandleExceedMaxTaskTitleLength.ts index ae0be4e798ed..3cc51208fa01 100644 --- a/src/hooks/useHandleExceedMaxTaskTitleLength.ts +++ b/src/hooks/useHandleExceedMaxTaskTitleLength.ts @@ -1,24 +1,14 @@ -import debounce from 'lodash/debounce'; -import {useCallback, useMemo, useState} from 'react'; +import {useCallback, useState} from 'react'; import CONST from '@src/CONST'; const useHandleExceedMaxTaskTitleLength = () => { const [hasExceededMaxTaskTitleLength, setHasExceededMaxTitleLength] = useState(false); - const handleValueChange = useCallback((value: string) => { - const match = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); - if (match) { - const title = match[3] ? match[3].trim().replace(/\n/g, ' ') : undefined; - const exceeded = title ? title.length > CONST.TITLE_CHARACTER_LIMIT : false; - setHasExceededMaxTitleLength(exceeded); - return true; - } - setHasExceededMaxTitleLength(false); - return false; + const validateTaskTitleMaxLength = useCallback((title: string) => { + const exceeded = title ? title.length > CONST.TITLE_CHARACTER_LIMIT : false; + setHasExceededMaxTitleLength(exceeded); }, []); - const validateTaskTitleMaxLength = useMemo(() => debounce(handleValueChange, CONST.TIMING.COMMENT_LENGTH_DEBOUNCE_TIME, {leading: true}), [handleValueChange]); - return {hasExceededMaxTaskTitleLength, validateTaskTitleMaxLength}; }; diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 107652cb72d2..e728abc67a92 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -1,4 +1,5 @@ import {useNavigation} from '@react-navigation/native'; +import lodashDebounce from 'lodash/debounce'; import noop from 'lodash/noop'; import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import type {MeasureInWindowOnSuccessCallback, NativeSyntheticEvent, TextInputFocusEventData, TextInputSelectionChangeEventData} from 'react-native'; @@ -127,6 +128,7 @@ function ReportActionCompose({ const navigation = useNavigation(); const [blockedFromConcierge] = useOnyx(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE); const [shouldShowComposeInput = true] = useOnyx(ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT); + const [isCreatingTaskComment, setIsCreatingTaskComment] = useState(false); /** * Updates the Highlight state of the composer @@ -310,14 +312,14 @@ function ReportActionCompose({ }, [onComposerFocus]); useEffect(() => { - if (hasExceededMaxTaskTitleLength) { + if (hasExceededMaxTaskTitleLength && isCreatingTaskComment) { setExceededMaxLength(CONST.TITLE_CHARACTER_LIMIT); } else if (hasExceededMaxCommentLength) { setExceededMaxLength(CONST.MAX_COMMENT_LENGTH); } else { setExceededMaxLength(null); } - }, [hasExceededMaxTaskTitleLength, hasExceededMaxCommentLength]); + }, [hasExceededMaxTaskTitleLength, hasExceededMaxCommentLength, isCreatingTaskComment]); // We are returning a callback here as we want to incoke the method on unmount only useEffect( @@ -407,17 +409,29 @@ function ReportActionCompose({ ], ); + const debouncedValidate = lodashDebounce( + (value: string) => { + const taskCommentMatch = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); + setIsCreatingTaskComment(!!taskCommentMatch); + if (taskCommentMatch) { + const title = taskCommentMatch?.[3] ? taskCommentMatch[3].trim().replace(/\n/g, ' ') : ''; + validateTaskTitleMaxLength(title); + } else { + validateCommentMaxLength(value, {reportID}); + } + }, + CONST.TIMING.COMMENT_LENGTH_DEBOUNCE_TIME, + {leading: true}, + ); + const onValueChange = useCallback( (value: string) => { if (value.length === 0 && isComposerFullSize) { Report.setIsComposerFullSize(reportID, false); } - if (validateTaskTitleMaxLength(value)) { - return; - } - validateCommentMaxLength(value, {reportID}); + debouncedValidate(value); }, - [isComposerFullSize, reportID, validateCommentMaxLength, validateTaskTitleMaxLength], + [isComposerFullSize, reportID, debouncedValidate], ); return ( diff --git a/src/pages/home/report/ReportActionItemMessageEdit.tsx b/src/pages/home/report/ReportActionItemMessageEdit.tsx index fbe51fa27acf..a172fb340f5a 100644 --- a/src/pages/home/report/ReportActionItemMessageEdit.tsx +++ b/src/pages/home/report/ReportActionItemMessageEdit.tsx @@ -109,6 +109,7 @@ function ReportActionItemMessageEdit( const [selection, setSelection] = useState({start: draft.length, end: draft.length, positionX: 0, positionY: 0}); const [isFocused, setIsFocused] = useState(false); const {hasExceededMaxCommentLength, validateCommentMaxLength} = useHandleExceedMaxCommentLength(); + const debouncedValidateCommentMaxLength = useMemo(() => lodashDebounce(validateCommentMaxLength, CONST.TIMING.COMMENT_LENGTH_DEBOUNCE_TIME), [validateCommentMaxLength]); const [modal, setModal] = useState({ willAlertModalBecomeVisible: false, isVisible: false, @@ -455,8 +456,8 @@ function ReportActionItemMessageEdit( ); useEffect(() => { - validateCommentMaxLength(draft, {reportID}); - }, [draft, reportID, validateCommentMaxLength]); + debouncedValidateCommentMaxLength(draft, {reportID}); + }, [draft, reportID, debouncedValidateCommentMaxLength]); useEffect(() => { // required for keeping last state of isFocused variable From 3f08b6548b82c129add6b99cff7f50d3ad3ec0db Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 28 Nov 2024 19:03:05 +0700 Subject: [PATCH 14/16] Remove unnecessary state --- src/hooks/useHandleExceedMaxCommentLength.ts | 2 +- src/hooks/useHandleExceedMaxTaskTitleLength.ts | 2 +- .../ReportActionCompose/ReportActionCompose.tsx | 12 ++++++------ 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/hooks/useHandleExceedMaxCommentLength.ts b/src/hooks/useHandleExceedMaxCommentLength.ts index be6381a64ea4..2c4fa1fd1886 100644 --- a/src/hooks/useHandleExceedMaxCommentLength.ts +++ b/src/hooks/useHandleExceedMaxCommentLength.ts @@ -19,7 +19,7 @@ const useHandleExceedMaxCommentLength = () => { [hasExceededMaxCommentLength], ); - return {hasExceededMaxCommentLength, validateCommentMaxLength}; + return {hasExceededMaxCommentLength, validateCommentMaxLength, setHasExceededMaxCommentLength}; }; export default useHandleExceedMaxCommentLength; diff --git a/src/hooks/useHandleExceedMaxTaskTitleLength.ts b/src/hooks/useHandleExceedMaxTaskTitleLength.ts index 3cc51208fa01..bb792a5da8b4 100644 --- a/src/hooks/useHandleExceedMaxTaskTitleLength.ts +++ b/src/hooks/useHandleExceedMaxTaskTitleLength.ts @@ -9,7 +9,7 @@ const useHandleExceedMaxTaskTitleLength = () => { setHasExceededMaxTitleLength(exceeded); }, []); - return {hasExceededMaxTaskTitleLength, validateTaskTitleMaxLength}; + return {hasExceededMaxTaskTitleLength, validateTaskTitleMaxLength, setHasExceededMaxTitleLength}; }; export default useHandleExceedMaxTaskTitleLength; diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index e728abc67a92..c6930b011eb8 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -128,7 +128,6 @@ function ReportActionCompose({ const navigation = useNavigation(); const [blockedFromConcierge] = useOnyx(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE); const [shouldShowComposeInput = true] = useOnyx(ONYXKEYS.SHOULD_SHOW_COMPOSE_INPUT); - const [isCreatingTaskComment, setIsCreatingTaskComment] = useState(false); /** * Updates the Highlight state of the composer @@ -174,8 +173,8 @@ function ReportActionCompose({ * Updates the composer when the comment length is exceeded * Shows red borders and prevents the comment from being sent */ - const {hasExceededMaxCommentLength, validateCommentMaxLength} = useHandleExceedMaxCommentLength(); - const {hasExceededMaxTaskTitleLength, validateTaskTitleMaxLength} = useHandleExceedMaxTaskTitleLength(); + const {hasExceededMaxCommentLength, validateCommentMaxLength, setHasExceededMaxCommentLength} = useHandleExceedMaxCommentLength(); + const {hasExceededMaxTaskTitleLength, validateTaskTitleMaxLength, setHasExceededMaxTitleLength} = useHandleExceedMaxTaskTitleLength(); const [exceededMaxLength, setExceededMaxLength] = useState(null); const suggestionsRef = useRef(null); @@ -312,14 +311,14 @@ function ReportActionCompose({ }, [onComposerFocus]); useEffect(() => { - if (hasExceededMaxTaskTitleLength && isCreatingTaskComment) { + if (hasExceededMaxTaskTitleLength) { setExceededMaxLength(CONST.TITLE_CHARACTER_LIMIT); } else if (hasExceededMaxCommentLength) { setExceededMaxLength(CONST.MAX_COMMENT_LENGTH); } else { setExceededMaxLength(null); } - }, [hasExceededMaxTaskTitleLength, hasExceededMaxCommentLength, isCreatingTaskComment]); + }, [hasExceededMaxTaskTitleLength, hasExceededMaxCommentLength]); // We are returning a callback here as we want to incoke the method on unmount only useEffect( @@ -412,11 +411,12 @@ function ReportActionCompose({ const debouncedValidate = lodashDebounce( (value: string) => { const taskCommentMatch = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); - setIsCreatingTaskComment(!!taskCommentMatch); if (taskCommentMatch) { const title = taskCommentMatch?.[3] ? taskCommentMatch[3].trim().replace(/\n/g, ' ') : ''; + setHasExceededMaxCommentLength(false); validateTaskTitleMaxLength(title); } else { + setHasExceededMaxTitleLength(false); validateCommentMaxLength(value, {reportID}); } }, From ad2272d45a2ecafbd4c7014195d93f93f9604dd5 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Thu, 28 Nov 2024 22:18:17 +0700 Subject: [PATCH 15/16] resolve performance issue --- .../report/ReportActionCompose/ReportActionCompose.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 4a5607ddc068..0baa04e474b7 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -408,7 +408,7 @@ function ReportActionCompose({ ], ); - const debouncedValidate = lodashDebounce( + const validateMaxLength = useCallback( (value: string) => { const taskCommentMatch = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); if (taskCommentMatch) { @@ -420,10 +420,11 @@ function ReportActionCompose({ validateCommentMaxLength(value, {reportID}); } }, - CONST.TIMING.COMMENT_LENGTH_DEBOUNCE_TIME, - {leading: true}, + [setHasExceededMaxCommentLength, setHasExceededMaxTitleLength, validateTaskTitleMaxLength, validateCommentMaxLength, reportID], ); + const debouncedValidate = useMemo(() => lodashDebounce(validateMaxLength, CONST.TIMING.COMMENT_LENGTH_DEBOUNCE_TIME, {leading: true}), [validateMaxLength]); + const onValueChange = useCallback( (value: string) => { if (value.length === 0 && isComposerFullSize) { From 36e4c3e1321c21d18602263425f84d6de919d644 Mon Sep 17 00:00:00 2001 From: Wildan Muhlis Date: Tue, 3 Dec 2024 09:51:01 +0700 Subject: [PATCH 16/16] Add optional chain --- .../home/report/ReportActionCompose/ReportActionCompose.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 0baa04e474b7..32371232ad56 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -410,7 +410,7 @@ function ReportActionCompose({ const validateMaxLength = useCallback( (value: string) => { - const taskCommentMatch = value.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); + const taskCommentMatch = value?.match(CONST.REGEX.TASK_TITLE_WITH_OPTONAL_SHORT_MENTION); if (taskCommentMatch) { const title = taskCommentMatch?.[3] ? taskCommentMatch[3].trim().replace(/\n/g, ' ') : ''; setHasExceededMaxCommentLength(false);