From 053231cf297eafa9cebb41c8c2d289333fcbb12b Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Mon, 17 Apr 2023 12:24:13 -0400 Subject: [PATCH 01/87] updated beta flag --- src/libs/Permissions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/Permissions.js b/src/libs/Permissions.js index d3e407260e20..d48512e31052 100644 --- a/src/libs/Permissions.js +++ b/src/libs/Permissions.js @@ -99,7 +99,7 @@ function canUsePasswordlessLogins(betas) { * @returns {Boolean} */ function canUseTasks(betas) { - return _.contains(betas, CONST.BETAS.TASKS) || _.contains(betas, CONST.BETAS.ALL); + return true; } export default { From db1e9868bfdedab4850a85a91cc0cdde9b311d58 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 18 Apr 2023 10:44:44 -0400 Subject: [PATCH 02/87] Added optimistic Task and creating assign Task --- src/libs/ReportUtils.js | 27 +++++++++++++++++++++++++++ src/libs/actions/Task.js | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/libs/actions/Task.js diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 7f7c16db795e..124e4f4bebf6 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1029,6 +1029,32 @@ function buildOptimisticAddCommentReportAction(text, file) { }; } +/** + * Builds an optimistic Task Report with a randomly generated reportID + * + * @param {String} ownerEmail - Email of the person generating the Task. + * @param {String} userEmail - Email of the other person participating in the Task. + * @param {String} chatReportID - Report ID of the chat where the Task is. + * @param {String} name - Task name. + * @param {String} description - Task description. + * + * @returns {Object} + */ + +function buildOptimisticTaskReport(ownerEmail, userEmail, parentReportID, parentReportActionID, name, description) { + return { + reportID: generateReportID(), + reportName: name, + description, + ownerEmail, + assignee: userEmail, + type: CONST.REPORT.TYPE.TASK, + parentReportID, + parentReportActionID, + state: CONST.REPORT.STATE.OPEN, + }; +} + /** * Builds an optimistic IOU report with a randomly generated reportID * @@ -1856,6 +1882,7 @@ export { isUnread, isUnreadWithMention, buildOptimisticWorkspaceChats, + buildOptimisticTaskReport, buildOptimisticChatReport, buildOptimisticClosedReportAction, buildOptimisticCreatedReportAction, diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js new file mode 100644 index 000000000000..4029b82de256 --- /dev/null +++ b/src/libs/actions/Task.js @@ -0,0 +1,39 @@ +import * as API from '../API'; +import * as Report from './Report'; + +/** + * Assign a task to a user + * @param {String} parentReportActionID + * @param {String} parentReportID + * @param {String} taskReportID + * @param {String} name + * @param {String} description + * @param {String} assignee + * + */ + +function assignTask(parentReportActionID, parentReportID, taskReportID, name, description, assignee) { + const parentReport = Report.getReportByID(parentReportID); + + const optimisticData = {}; + + const successData = {}; + + const failureData = {}; + + return API.write( + 'CreateTask', + { + parentReportActionID, + parentReportID, + taskReportID, + name, + description, + assignee, + }, + {optimisticData, successData, failureData}, + ); +} + +// eslint-disable-next-line import/prefer-default-export +export {assignTask}; From cff5490dd73bdef789843751593a890db046bc63 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Thu, 20 Apr 2023 09:13:56 -0400 Subject: [PATCH 03/87] created assignTask --- src/libs/ReportUtils.js | 6 ++--- src/libs/actions/Task.js | 53 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 124e4f4bebf6..bacd45f79d96 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -1033,7 +1033,7 @@ function buildOptimisticAddCommentReportAction(text, file) { * Builds an optimistic Task Report with a randomly generated reportID * * @param {String} ownerEmail - Email of the person generating the Task. - * @param {String} userEmail - Email of the other person participating in the Task. + * @param {String} assignee - Email of the other person participating in the Task. * @param {String} chatReportID - Report ID of the chat where the Task is. * @param {String} name - Task name. * @param {String} description - Task description. @@ -1041,13 +1041,13 @@ function buildOptimisticAddCommentReportAction(text, file) { * @returns {Object} */ -function buildOptimisticTaskReport(ownerEmail, userEmail, parentReportID, parentReportActionID, name, description) { +function buildOptimisticTaskReport(ownerEmail, assignee, parentReportID, parentReportActionID, name, description) { return { reportID: generateReportID(), reportName: name, description, ownerEmail, - assignee: userEmail, + assignee, type: CONST.REPORT.TYPE.TASK, parentReportID, parentReportActionID, diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 4029b82de256..b1308f85c3fd 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -1,5 +1,7 @@ import * as API from '../API'; +import * as ReportUtils from '../ReportUtils'; import * as Report from './Report'; +import CONST from '../../CONST'; /** * Assign a task to a user @@ -9,27 +11,68 @@ import * as Report from './Report'; * @param {String} name * @param {String} description * @param {String} assignee + * @param {String} ownerEmail * */ -function assignTask(parentReportActionID, parentReportID, taskReportID, name, description, assignee) { - const parentReport = Report.getReportByID(parentReportID); +function assignTask(parentReportActionID, parentReportID, taskReportID, name, description, assignee, assigneeChatReportID) { + let parentReport; - const optimisticData = {}; + // If there isn't a parentReport provided and assignee is given, we need to optimistically create a new parent report + // between the task creator and the assignee. + if (!parentReportID && assignee) { + parentReport = ReportUtils.getChatByParticipants([assignee]); + } + + if (!parentReportID && !parentReport && assignee) { + parentReport = ReportUtils.buildOptimisticChatReport([assignee]); + } + + // If parentReport is defined, use its reportID, otherwise, use the provided parentReportID + const finalParentReportID = parentReport ? parentReport.reportID : parentReportID; + + // Open the parent report if assignee is given + if (assignee) { + Report.openReport(finalParentReportID, [assignee]); + } + + const ownerEmail = getOwnerEmail(); // need to figure out how to get the owner email + const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport(ownerEmail, assignee, finalParentReportID, parentReportActionID, name, description); + + // AddCommentReportAction on the parent chat report + const optimisticAddCommentReportAction = ReportUtils.buildOptimisticAddCommentReportAction(finalParentReportID, optimisticTaskReport.reportID); + + const optimisticData = { + taskReport: optimisticTaskReport, + addCommentReportAction: optimisticAddCommentReportAction, + }; const successData = {}; - const failureData = {}; + const failureData = { + undoActions: [ + { + method: 'DELETE_TASK_REPORT', + reportID: optimisticTaskReport.reportID, + }, + { + method: 'DELETE_ADD_COMMENT_REPORT_ACTION', + reportID: finalParentReportID, + actionID: optimisticAddCommentReportAction.actionID, + }, + ], + }; return API.write( 'CreateTask', { parentReportActionID, - parentReportID, + parentReportID: finalParentReportID, taskReportID, name, description, assignee, + assigneeChatReportID, }, {optimisticData, successData, failureData}, ); From ba8a856872a440e50fd41760e724619bd17844de Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Thu, 20 Apr 2023 10:06:15 -0400 Subject: [PATCH 04/87] Updated the optimistic data --- src/libs/actions/Task.js | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index b1308f85c3fd..374d1e3248dc 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -1,7 +1,6 @@ import * as API from '../API'; import * as ReportUtils from '../ReportUtils'; import * as Report from './Report'; -import CONST from '../../CONST'; /** * Assign a task to a user @@ -15,10 +14,10 @@ import CONST from '../../CONST'; * */ -function assignTask(parentReportActionID, parentReportID, taskReportID, name, description, assignee, assigneeChatReportID) { +function assignTask(parentReportActionID, parentReportID, taskReportID, name, description, assignee, assigneeChatReportID, ownerEmail) { let parentReport; - // If there isn't a parentReport provided and assignee is given, we need to optimistically create a new parent report + // If there isn't a parentReportId provided and assignee is given, we need to optimistically create a new parent chat report // between the task creator and the assignee. if (!parentReportID && assignee) { parentReport = ReportUtils.getChatByParticipants([assignee]); @@ -36,16 +35,14 @@ function assignTask(parentReportActionID, parentReportID, taskReportID, name, de Report.openReport(finalParentReportID, [assignee]); } - const ownerEmail = getOwnerEmail(); // need to figure out how to get the owner email + const optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(ownerEmail); + const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport(ownerEmail, assignee, finalParentReportID, parentReportActionID, name, description); // AddCommentReportAction on the parent chat report const optimisticAddCommentReportAction = ReportUtils.buildOptimisticAddCommentReportAction(finalParentReportID, optimisticTaskReport.reportID); - const optimisticData = { - taskReport: optimisticTaskReport, - addCommentReportAction: optimisticAddCommentReportAction, - }; + const optimisticData = [optimisticCreatedAction, optimisticTaskReport, optimisticAddCommentReportAction]; const successData = {}; @@ -55,6 +52,11 @@ function assignTask(parentReportActionID, parentReportID, taskReportID, name, de method: 'DELETE_TASK_REPORT', reportID: optimisticTaskReport.reportID, }, + { + method: 'DELETE_REPORT_ACTION', + reportID: finalParentReportID, + actionID: optimisticCreatedAction.actionID, + }, { method: 'DELETE_ADD_COMMENT_REPORT_ACTION', reportID: finalParentReportID, From 6947298bc76376cdafd608d0bfdc5ad0afb23281 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Fri, 21 Apr 2023 14:46:52 -0400 Subject: [PATCH 05/87] converted necessary props for selector to hooks --- src/languages/en.js | 116 +++++++++++++++++++++------------------ src/languages/es.js | 53 +++++++++++------- src/libs/actions/Task.js | 24 +++++++- src/pages/NewTaskPage.js | 101 ++++++++++++++++++++++++++++------ 4 files changed, 202 insertions(+), 92 deletions(-) diff --git a/src/languages/en.js b/src/languages/en.js index 5422adba1af9..233bb2fc0f6a 100755 --- a/src/languages/en.js +++ b/src/languages/en.js @@ -74,7 +74,7 @@ export default { country: 'Country', zip: 'Zip code', zipPostCode: 'Zip / Postcode', - whatThis: 'What\'s this?', + whatThis: "What's this?", iAcceptThe: 'I accept the ', remove: 'Remove', admin: 'Admin', @@ -83,7 +83,7 @@ export default { notifications: 'Notifications', na: 'N/A', noResultsFound: 'No results found', - timePrefix: 'It\'s', + timePrefix: "It's", conjunctionFor: 'for', todayAt: 'Today at', tomorrowAt: 'Tomorrow at', @@ -115,8 +115,8 @@ export default { bankAccount: 'Bank account', join: 'Join', decline: 'Decline', - transferBalance: 'Transfer balance', - cantFindAddress: 'Can\'t find your address? ', + transferBalance: 'Transfer Balance', + cantFindAddress: "Can't find your address? ", enterManually: 'Enter it manually', message: 'Message ', leaveRoom: 'Leave room', @@ -137,7 +137,7 @@ export default { }, attachmentPicker: { cameraPermissionRequired: 'Camera access', - expensifyDoesntHaveAccessToCamera: 'Expensify can\'t take photos without access to your camera. Tap Settings to update permissions.', + expensifyDoesntHaveAccessToCamera: "Expensify can't take photos without access to your camera. Tap Settings to update permissions.", attachmentError: 'Attachment error', errorWhileSelectingAttachment: 'An error occurred while selecting an attachment, please try again', errorWhileSelectingCorruptedImage: 'An error occurred while selecting a corrupted attachment, please try another file', @@ -186,7 +186,7 @@ export default { moneyRequestConfirmationList: { whoPaid: 'Who paid?', whoWasThere: 'Who was there?', - whatsItFor: 'What\'s it for?', + whatsItFor: "What's it for?", }, iOUCurrencySelection: { selectCurrency: 'Select a currency', @@ -207,7 +207,7 @@ export default { getStarted: 'Get started below.', welcomeBack: 'Welcome back!', welcome: 'Welcome!', - phrase2: 'Money talks. And now that chat and payments are in one place, it\'s also easy.', + phrase2: "Money talks. And now that chat and payments are in one place, it's also easy.", phrase3: 'Your payments get to you as fast as you can get your point across.', enterPassword: 'Please enter your password', newFaceEnterMagicCode: ({login}) => `It's always great to see a new face around here! Please enter the magic code sent to ${login}`, @@ -261,7 +261,7 @@ export default { }, reportActionsView: { beginningOfArchivedRoomPartOne: 'You missed the party in ', - beginningOfArchivedRoomPartTwo: ', there\'s nothing to see here.', + beginningOfArchivedRoomPartTwo: ", there's nothing to see here.", beginningOfChatHistoryDomainRoomPartOne: ({domainRoom}) => `Collaboration with everyone at ${domainRoom} starts here! 🎉\nUse `, beginningOfChatHistoryDomainRoomPartTwo: ' to chat with colleagues, share tips, and ask questions.', beginningOfChatHistoryAdminRoomPartOne: ({workspaceName}) => `Collaboration among ${workspaceName} admins starts here! 🎉\nUse `, @@ -316,12 +316,12 @@ export default { pay: 'Pay', viewDetails: 'View details', settleExpensify: 'Pay with Expensify', - settleElsewhere: 'I\'ll settle up elsewhere', + settleElsewhere: "I'll settle up elsewhere", settlePaypalMe: 'Pay with PayPal.me', requestAmount: ({amount}) => `request ${amount}`, splitAmount: ({amount}) => `split ${amount}`, noReimbursableExpenses: 'This report has an invalid amount', - pendingConversionMessage: 'Total will update when you\'re back online', + pendingConversionMessage: "Total will update when you're back online", error: { invalidSplit: 'Split amounts do not equal total amount', other: 'Unexpected error, please try again later', @@ -378,7 +378,8 @@ export default { pleaseVerify: 'Please verify this contact method', getInTouch: "Whenever we need to get in touch with you, we'll use this contact method.", enterMagicCode: ({contactMethod}) => `Please enter the magic code sent to ${contactMethod}`, - yourDefaultContactMethod: 'This is your current default contact method. You will not be able to delete this contact method until you set an alternative default by selecting another contact method and pressing “Set as default”.', + yourDefaultContactMethod: + 'This is your current default contact method. You will not be able to delete this contact method until you set an alternative default by selecting another contact method and pressing “Set as default”.', removeContactMethod: 'Remove contact method', removeAreYouSure: 'Are you sure you want to remove this contact method? This action cannot be undone.', resendMagicCode: 'Resend magic code', @@ -444,7 +445,7 @@ export default { }, security: 'Security', signOut: 'Sign out', - signOutConfirmationText: 'You\'ll lose any offline changes if you sign-out.', + signOutConfirmationText: "You'll lose any offline changes if you sign-out.", versionLetter: 'v', readTheTermsAndPrivacy: { phrase1: 'Read the', @@ -459,7 +460,8 @@ export default { reasonForLeavingPrompt: 'We’d hate to see you go! Would you kindly tell us why, so we can improve?', enterMessageHere: 'Enter message here', closeAccountWarning: 'Closing your account cannot be undone.', - closeAccountPermanentlyDeleteData: 'This will permanently delete all of your unsubmitted expense data and will cancel and decline any outstanding money requests. Are you sure you want to delete the account?', + closeAccountPermanentlyDeleteData: + 'This will permanently delete all of your unsubmitted expense data and will cancel and decline any outstanding money requests. Are you sure you want to delete the account?', enterDefaultContactToConfirm: 'Please type your default contact method to confirm you wish to close your account. Your default contact method is:', enterDefaultContact: 'Enter your default contact method', defaultContact: 'Default contact method:', @@ -589,7 +591,7 @@ export default { openJobs: 'open jobs', heroHeading: 'Split bills\nand chat with friends.', heroDescription: { - phrase1: 'Money talks. And now that chat and payments are in one place, it\'s also easy. Your payments get to you as fast as you can get your point across.', + phrase1: "Money talks. And now that chat and payments are in one place, it's also easy. Your payments get to you as fast as you can get your point across.", phrase2: 'The New Expensify is open source. View', phrase3: 'the code', phrase4: 'View', @@ -630,7 +632,8 @@ export default { incorrect2fa: 'Incorrect two factor authentication code. Please try again.', twoFactorAuthenticationEnabled: 'You have 2FA enabled on this account. Please sign in using your email or phone number.', invalidLoginOrPassword: 'Invalid login or password. Please try again or reset your password.', - unableToResetPassword: 'We were unable to change your password. This is likely due to an expired password reset link in an old password reset email. We have emailed you a new link so you can try again. Check your Inbox and your Spam folder; it should arrive in just a few minutes.', + unableToResetPassword: + 'We were unable to change your password. This is likely due to an expired password reset link in an old password reset email. We have emailed you a new link so you can try again. Check your Inbox and your Spam folder; it should arrive in just a few minutes.', noAccess: 'You do not have access to this application. Please add your GitHub username for access.', accountLocked: 'Your account has been locked after too many unsuccessful attempts. Please try again after 1 hour.', fallback: 'Something went wrong. Please try again later.', @@ -641,7 +644,7 @@ export default { error: { invalidFormatEmailLogin: 'The email entered is invalid. Please fix the format and try again.', }, - cannotGetAccountDetails: 'Couldn\'t retrieve account details, please try to sign in again.', + cannotGetAccountDetails: "Couldn't retrieve account details, please try to sign in again.", loginForm: 'Login form', notYou: ({user}) => `Not ${user}?`, }, @@ -693,7 +696,7 @@ export default { iouReportNotFound: 'The payment details you are looking for cannot be found.', notHere: "Hmm... it's not here", pageNotFound: 'That page is nowhere to be found.', - noAccess: 'You don\'t have access to this chat', + noAccess: "You don't have access to this chat", goBackHome: 'Go back to Home page', }, setPasswordPage: { @@ -718,7 +721,8 @@ export default { toGetStarted: 'Add a bank account and issue corporate cards, reimburse expenses, collect invoice payments, and pay bills, all from one place.', plaidBodyCopy: 'Give your employees an easier way to pay - and get paid back - for company expenses.', checkHelpLine: 'Your routing number and account number can be found on a check for the account.', - validateAccountError: 'In order to finish setting up your bank account, you must validate your account. Please check your email to validate your account, and return here to finish up!', + validateAccountError: + 'In order to finish setting up your bank account, you must validate your account. Please check your email to validate your account, and return here to finish up!', hasPhoneLoginError: 'To add a verified bank account please ensure your primary login is a valid email and try again. You can add your phone number as a secondary login.', hasBeenThrottledError: 'There was an error adding your bank account. Please wait a few minutes and try again.', buttonConfirm: 'Got it', @@ -786,10 +790,10 @@ export default { cameraRequestMessage: 'You have not granted us camera access. We need access to complete verification.', originalDocumentNeeded: 'Please upload an original image of your ID rather than a screenshot or scanned image.', documentNeedsBetterQuality: 'Your ID appears to be damaged or has missing security features. Please upload an original image of an undamaged ID that is entirely visible.', - imageNeedsBetterQuality: 'There\'s an issue with the image quality of your ID. Please upload a new image where your entire ID can be seen clearly.', - selfieIssue: 'There\'s an issue with your selfie/video. Please upload a new selfie/video in real time.', - selfieNotMatching: 'Your selfie/video doesn\'t match your ID. Please upload a new selfie/video where your face can be clearly seen.', - selfieNotLive: 'Your selfie/video doesn\'t appear to be a live photo/video. Please upload a live selfie/video.', + imageNeedsBetterQuality: "There's an issue with the image quality of your ID. Please upload a new image where your entire ID can be seen clearly.", + selfieIssue: "There's an issue with your selfie/video. Please upload a new selfie/video in real time.", + selfieNotMatching: "Your selfie/video doesn't match your ID. Please upload a new selfie/video where your face can be clearly seen.", + selfieNotLive: "Your selfie/video doesn't appear to be a live photo/video. Please upload a live selfie/video.", }, additionalDetailsStep: { headerTitle: 'Additional details', @@ -801,10 +805,10 @@ export default { legalLastNameLabel: 'Legal last name', selectAnswer: 'You need to select a response to proceed.', ssnFull9Error: 'Please enter a valid 9 digit SSN', - needSSNFull9: 'We\'re having trouble verifying your SSN. Please enter the full 9 digits of your SSN.', + needSSNFull9: "We're having trouble verifying your SSN. Please enter the full 9 digits of your SSN.", weCouldNotVerify: 'We could not verify', pleaseFixIt: 'Please fix this information before continuing.', - failedKYCTextBefore: 'We weren\'t able to successfully verify your identity. Please try again later and reach out to ', + failedKYCTextBefore: "We weren't able to successfully verify your identity. Please try again later and reach out to ", failedKYCTextAfter: ' if you have any questions.', }, termsStep: { @@ -853,23 +857,24 @@ export default { customerServiceDetails: 'There are no customer service fees.', inactivityDetails: 'There is no inactivity fee.', sendingFundsTitle: 'Sending funds to another account holder', - sendingFundsDetails: 'There is no fee to send funds to another account holder using your balance, ' - + 'bank account, or debit card.', - electronicFundsStandardDetails: 'There is no fee to transfer funds from your Expensify Wallet ' + sendingFundsDetails: 'There is no fee to send funds to another account holder using your balance, ' + 'bank account, or debit card.', + electronicFundsStandardDetails: + 'There is no fee to transfer funds from your Expensify Wallet ' + 'to your bank account using the standard option. This transfer usually completes within 1-3 business' + ' days.', - electronicFundsInstantDetails: 'There is a fee to transfer funds from your Expensify Wallet to ' + electronicFundsInstantDetails: + 'There is a fee to transfer funds from your Expensify Wallet to ' + 'your linked debit card using the instant transfer option. This transfer usually completes within ' + 'several minutes. The fee is 1.5% of the transfer amount (with a minimum fee of $0.25).', - fdicInsuranceBancorp: 'Your funds are eligible for FDIC insurance. Your funds will be held at or ' + fdicInsuranceBancorp: + 'Your funds are eligible for FDIC insurance. Your funds will be held at or ' + 'transferred to The Bancorp Bank, an FDIC-insured institution. Once there, your funds are insured up ' + 'to $250,000 by the FDIC in the event The Bancorp Bank fails. See', fdicInsuranceBancorp2: 'for details.', contactExpensifyPayments: 'Contact Expensify Payments by calling +1 833-400-0904, by email at', contactExpensifyPayments2: 'or sign in at', generalInformation: 'For general information about prepaid accounts, visit', - generalInformation2: 'If you have a complaint about a prepaid account, call the Consumer Financial ' - + 'Protection Bureau at 1-855-411-2372 or visit', + generalInformation2: 'If you have a complaint about a prepaid account, call the Consumer Financial ' + 'Protection Bureau at 1-855-411-2372 or visit', printerFriendlyView: 'View printer-friendly version', automated: 'Automated', liveAgent: 'Live Agent', @@ -882,7 +887,7 @@ export default { activatedTitle: 'Wallet activated!', activatedMessage: 'Congrats, your wallet is set up and ready to make payments.', checkBackLaterTitle: 'Just a minute...', - checkBackLaterMessage: 'We\'re still reviewing your information. Please check back later.', + checkBackLaterMessage: "We're still reviewing your information. Please check back later.", continueToPayment: 'Continue to payment', continueToTransfer: 'Continue to transfer', }, @@ -924,13 +929,14 @@ export default { maxAttemptsReached: 'Validation for this bank account has been disabled due to too many incorrect attempts.', description: 'A day or two after you add your account to Expensify we send three (3) transactions to your account. They have a merchant line like "Expensify, Inc. Validation".', descriptionCTA: 'Please enter each transaction amount in the fields below. Example: 1.51.', - reviewingInfo: 'Thanks! We\'re reviewing your information, and will be in touch shortly. Please check your chat with Concierge ', + reviewingInfo: "Thanks! We're reviewing your information, and will be in touch shortly. Please check your chat with Concierge ", forNextSteps: ' for next steps to finish setting up your bank account.', - letsChatCTA: 'Yes, let\'s chat', + letsChatCTA: "Yes, let's chat", letsChatText: 'Thanks for doing that. We need your help verifying a few pieces of information, but we can work this out quickly over chat. Ready?', - letsChatTitle: 'Let\'s chat!', + letsChatTitle: "Let's chat!", enable2FATitle: 'Prevent fraud, enable two-factor authentication!', - enable2FAText: 'We take your security seriously, so please set up two-factor authentication for your account now. That will allow us to dispute Expensify Card digital transactions, and will reduce your risk for fraud.', + enable2FAText: + 'We take your security seriously, so please set up two-factor authentication for your account now. That will allow us to dispute Expensify Card digital transactions, and will reduce your risk for fraud.', secureYourAccount: 'Secure your account', }, beneficialOwnersStep: { @@ -953,7 +959,7 @@ export default { explanationLine: 'We’re taking a look at your information. You will be able to continue with next steps shortly.', }, session: { - offlineMessageRetry: 'Looks like you\'re offline. Please check your connection and try again.', + offlineMessageRetry: "Looks like you're offline. Please check your connection and try again.", }, workspace: { common: { @@ -980,7 +986,7 @@ export default { }, emptyWorkspace: { title: 'Create a new workspace', - subtitle: 'Workspaces are where you\'ll chat with your team, reimburse expenses, issue cards, send invoices, pay bills, and more — all in one place.', + subtitle: "Workspaces are where you'll chat with your team, reimburse expenses, issue cards, send invoices, pay bills, and more — all in one place.", }, new: { newWorkspace: 'New workspace', @@ -1025,18 +1031,18 @@ export default { captureNoVBACopyBeforeEmail: 'Ask your workspace members to forward receipts to ', captureNoVBACopyAfterEmail: ' and download the Expensify App to track cash expenses on the go.', unlockNoVBACopy: 'Connect a bank account to reimburse your workspace members online.', - fastReimbursementsVBACopy: 'You\'re all set to reimburse receipts from your bank account!', + fastReimbursementsVBACopy: "You're all set to reimburse receipts from your bank account!", updateCustomUnitError: "Your changes couldn't be saved. The workspace was modified while you were offline, please try again.", }, bills: { manageYourBills: 'Manage your bills', askYourVendorsBeforeEmail: 'Ask your vendors to forward their invoices to ', - askYourVendorsAfterEmail: ' and we\'ll scan them for you to pay.', + askYourVendorsAfterEmail: " and we'll scan them for you to pay.", viewAllBills: 'View all bills', unlockOnlineBillPayment: 'Unlock online bill payment', unlockNoVBACopy: 'Connect your bank account to pay bills online for free!', hassleFreeBills: 'Hassle-free bills!', - VBACopy: 'You\'re all set to make payments from your bank account!', + VBACopy: "You're all set to make payments from your bank account!", }, invoices: { invoiceClientsAndCustomers: 'Invoice clients and customers', @@ -1045,7 +1051,7 @@ export default { unlockOnlineInvoiceCollection: 'Unlock online invoice collection', unlockNoVBACopy: 'Connect your bank account to accept online payments for invoices - by ACH or credit card - to be deposited straight into your account.', moneyBackInAFlash: 'Money back, in a flash!', - unlockVBACopy: 'You\'re all set to accept payments by ACH or credit card!', + unlockVBACopy: "You're all set to accept payments by ACH or credit card!", viewUnpaidInvoices: 'View unpaid invoices', sendInvoice: 'Send invoice', }, @@ -1075,24 +1081,26 @@ export default { nameIsRequiredError: 'You need to define a name for your workspace.', currencyInputLabel: 'Default currency', currencyInputHelpText: 'All expenses on this workspace will be converted to this currency.', - currencyInputDisabledText: 'The default currency can\'t be changed because this workspace is linked to a USD bank account.', + currencyInputDisabledText: "The default currency can't be changed because this workspace is linked to a USD bank account.", save: 'Save', genericFailureMessage: 'An error occurred updating the workspace, please try again.', avatarUploadFailureMessage: 'An error occurred uploading the avatar, please try again.', }, bankAccount: { continueWithSetup: 'Continue with setup', - youreAlmostDone: 'You\'re almost done setting up your bank account, which will let you issue corporate cards, reimburse expenses, collect invoices, and pay bills all from the same bank account.', + youreAlmostDone: + "You're almost done setting up your bank account, which will let you issue corporate cards, reimburse expenses, collect invoices, and pay bills all from the same bank account.", streamlinePayments: 'Streamline payments', oneMoreThing: 'One more thing!', - allSet: 'You\'re all set!', - accountDescriptionNoCards: 'This bank account will be used to reimburse expenses, collect invoices, and pay bills all from the same account.\n\nPlease add a work email address as a secondary login to enable the Expensify Card.', + allSet: "You're all set!", + accountDescriptionNoCards: + 'This bank account will be used to reimburse expenses, collect invoices, and pay bills all from the same account.\n\nPlease add a work email address as a secondary login to enable the Expensify Card.', accountDescriptionWithCards: 'This bank account will be used to issue corporate cards, reimburse expenses, collect invoices, and pay bills all from the same account.', addWorkEmail: 'Add work email address', - letsFinishInChat: 'Let\'s finish in chat!', + letsFinishInChat: "Let's finish in chat!", almostDone: 'Almost done!', disconnectBankAccount: 'Disconnect bank account', - noLetsStartOver: 'No, let\'s start over', + noLetsStartOver: "No, let's start over", startOver: 'Start over', yesDisconnectMyBankAccount: 'Yes, disconnect my bank account', yesStartOver: 'Yes, start over', @@ -1104,7 +1112,7 @@ export default { }, getAssistancePage: { title: 'Get assistance', - subtitle: 'We\'re here to clear your path to greatness!', + subtitle: "We're here to clear your path to greatness!", description: 'Choose from the support options below:', chatWithConcierge: 'Chat with Concierge', scheduleSetupCall: 'Schedule a setup call', @@ -1153,15 +1161,17 @@ export default { newTaskPage: { task: 'Task', assignTask: 'Assign task', + assignTo: 'To', title: 'Title', description: 'Description', shareIn: 'Share in', pleaseEnterTaskName: 'Please enter a title', markAsComplete: 'Mark as complete', markAsIncomplete: 'Mark as incomplete', + pleaseEnterTaskAssignee: 'Please select an assignee', }, statementPage: { - generatingPDF: 'We\'re generating your PDF right now. Please come back later!', + generatingPDF: "We're generating your PDF right now. Please come back later!", }, keyboardShortcutModal: { title: 'Keyboard shortcuts', @@ -1198,7 +1208,7 @@ export default { }, permissionError: { title: 'Storage access', - message: 'Expensify can\'t save attachments without storage access. Tap Settings to update permissions.', + message: "Expensify can't save attachments without storage access. Tap Settings to update permissions.", }, }, desktopApplicationMenu: { @@ -1214,7 +1224,7 @@ export default { checkForUpdatesModal: { available: { title: 'Update Available', - message: 'The new version will be available shortly. We\'ll notify you when we\'re ready to update.', + message: "The new version will be available shortly. We'll notify you when we're ready to update.", soundsGood: 'Sounds good', }, notAvailable: { @@ -1224,7 +1234,7 @@ export default { }, error: { title: 'Update Check Failed', - message: 'We couldn\'t look for an update. Please check again in a bit!', + message: "We couldn't look for an update. Please check again in a bit!", }, }, report: { diff --git a/src/languages/es.js b/src/languages/es.js index 1fb5b1c1b826..93cfdad5ee0e 100644 --- a/src/languages/es.js +++ b/src/languages/es.js @@ -377,7 +377,8 @@ export default { pleaseVerify: 'Por favor, verifica este método de contacto', getInTouch: 'Utilizaremos este método de contacto cuando necesitemos contactarte.', enterMagicCode: ({contactMethod}) => `Por favor, introduce el código mágico enviado a ${contactMethod}`, - yourDefaultContactMethod: 'Este es tu método de contacto predeterminado. No podrás eliminarlo hasta que añadas otro método de contacto y lo marques como predeterminado pulsando "Establecer como predeterminado".', + yourDefaultContactMethod: + 'Este es tu método de contacto predeterminado. No podrás eliminarlo hasta que añadas otro método de contacto y lo marques como predeterminado pulsando "Establecer como predeterminado".', removeContactMethod: 'Eliminar método de contacto', removeAreYouSure: '¿Estás seguro de que quieres eliminar este método de contacto? Esta acción no se puede deshacer.', resendMagicCode: 'Reenviar código mágico', @@ -458,7 +459,8 @@ export default { reasonForLeavingPrompt: '¡Lamentamos verte partir! ¿Serías tan amable de decirnos por qué, para que podamos mejorar?', enterMessageHere: 'Escribe aquí tu mensaje', closeAccountWarning: 'Una vez cerrada tu cuenta no se puede revertir.', - closeAccountPermanentlyDeleteData: 'Esta acción eliminará permanentemente toda la información de tus gastos no enviados y cancelará o rechazará cualquier solicitud de dinero pendiente. ¿Estás seguro de que quieres eliminar tu cuenta?', + closeAccountPermanentlyDeleteData: + 'Esta acción eliminará permanentemente toda la información de tus gastos no enviados y cancelará o rechazará cualquier solicitud de dinero pendiente. ¿Estás seguro de que quieres eliminar tu cuenta?', enterDefaultContactToConfirm: 'Por favor, escribe tu método de contacto predeterminado para confirmar que deseas eliminar tu cuenta. Tu método de contacto predeterminado es:', enterDefaultContact: 'Tu método de contacto predeterminado', defaultContact: 'Método de contacto predeterminado:', @@ -559,7 +561,8 @@ export default { }, priorityModePage: { priorityMode: 'Modo prioridad', - explainerText: 'Elija si desea mostrar por defecto todos los chats ordenados desde el más reciente y con los elementos anclados en la parte superior, o elija el modo #concentración, con los elementos no leídos anclados en la parte superior y ordenados alfabéticamente.', + explainerText: + 'Elija si desea mostrar por defecto todos los chats ordenados desde el más reciente y con los elementos anclados en la parte superior, o elija el modo #concentración, con los elementos no leídos anclados en la parte superior y ordenados alfabéticamente.', priorityModes: { default: { label: 'Más recientes', @@ -629,7 +632,8 @@ export default { incorrect2fa: 'Código de autenticación de 2 factores incorrecto. Por favor, inténtalo de nuevo', twoFactorAuthenticationEnabled: 'Tienes autenticación de 2 factores activada en esta cuenta. Por favor, conéctate usando tu email o número de teléfono', invalidLoginOrPassword: 'Usuario o clave incorrectos. Por favor, inténtalo de nuevo o restablece la contraseña', - unableToResetPassword: 'No se pudo cambiar tu clave. Probablemente porque el enlace para restablecer la contrasenña ha expirado. Te hemos enviado un nuevo enlace. Comprueba tu bandeja de entrada y carpeta de Spam', + unableToResetPassword: + 'No se pudo cambiar tu clave. Probablemente porque el enlace para restablecer la contrasenña ha expirado. Te hemos enviado un nuevo enlace. Comprueba tu bandeja de entrada y carpeta de Spam', noAccess: 'No tienes acceso a esta aplicación. Por favor, agrega tu usuario de GitHub para acceder', accountLocked: 'Tu cuenta ha sido bloqueada tras varios intentos fallidos. Por favor, inténtalo de nuevo dentro de una hora', fallback: 'Ha ocurrido un error. Por favor, inténtalo mas tarde', @@ -717,8 +721,10 @@ export default { toGetStarted: 'Añade una cuenta bancaria y emite tarjetas corporativas, reembolsa gastos y cobra y paga facturas, todo desde un mismo lugar.', plaidBodyCopy: 'Ofrezca a sus empleados una forma más sencilla de pagar - y recuperar - los gastos de la empresa.', checkHelpLine: 'Su número de ruta y número de cuenta se pueden encontrar en un cheque de la cuenta bancaria.', - validateAccountError: 'Para terminar de configurar tu cuenta bancaria, debes validar tu cuenta de Expensify. Por favor, revisa tu correo electrónico para validar tu cuenta y vuelve aquí para continuar.', - hasPhoneLoginError: 'Para agregar una cuenta bancaria verificada, asegúrate de que tu nombre de usuario principal sea un correo electrónico válido y vuelve a intentarlo. Puedes agregar tu número de teléfono como nombre de usuario secundario.', + validateAccountError: + 'Para terminar de configurar tu cuenta bancaria, debes validar tu cuenta de Expensify. Por favor, revisa tu correo electrónico para validar tu cuenta y vuelve aquí para continuar.', + hasPhoneLoginError: + 'Para agregar una cuenta bancaria verificada, asegúrate de que tu nombre de usuario principal sea un correo electrónico válido y vuelve a intentarlo. Puedes agregar tu número de teléfono como nombre de usuario secundario.', hasBeenThrottledError: 'Se produjo un error al intentar agregar tu cuenta bancaria. Por favor, espera unos minutos e inténtalo de nuevo.', buttonConfirm: 'OK', error: { @@ -784,7 +790,8 @@ export default { cameraPermissionsNotGranted: 'No has habilitado los permisos para acceder a la cámara', cameraRequestMessage: 'No has habilitado los permisos para acceder a la cámara. Necesitamos acceso para completar la verificaciôn.', originalDocumentNeeded: 'Por favor, sube una imagen original de tu identificación en lugar de una captura de pantalla o imagen escaneada.', - documentNeedsBetterQuality: 'Parece que tu identificación esta dañado o le faltan características de seguridad. Por favor, sube una imagen de tu documento sin daños y que se vea completamente.', + documentNeedsBetterQuality: + 'Parece que tu identificación esta dañado o le faltan características de seguridad. Por favor, sube una imagen de tu documento sin daños y que se vea completamente.', imageNeedsBetterQuality: 'Hay un problema con la calidad de la imagen de tu identificación. Por favor, sube una nueva imagen donde el identificación se vea con claridad.', selfieIssue: 'Hay un problema con tu selfie/video. Por favor, sube un nuevo selfie/video grabado en el momento', selfieNotMatching: 'Tu selfie/video no concuerda con tu identificación. Por favor, sube un nuevo selfie/video donde se vea tu cara con claridad.', @@ -852,25 +859,25 @@ export default { customerServiceDetails: 'No hay tarifas de servicio al cliente.', inactivityDetails: 'No hay tarifa de inactividad.', sendingFundsTitle: 'Enviar fondos a otro titular de cuenta', - sendingFundsDetails: 'No se aplica ningún cargo por enviar fondos a otro titular de cuenta utilizando su ' - + 'saldo cuenta bancaria o tarjeta de débito', - electronicFundsStandardDetails: 'No hay cargo por transferir fondos desde su billetera Expensify ' + sendingFundsDetails: 'No se aplica ningún cargo por enviar fondos a otro titular de cuenta utilizando su ' + 'saldo cuenta bancaria o tarjeta de débito', + electronicFundsStandardDetails: + 'No hay cargo por transferir fondos desde su billetera Expensify ' + 'a su cuenta bancaria utilizando la opción estándar. Esta transferencia generalmente se completa en' + '1-3 negocios días.', - electronicFundsInstantDetails: 'Hay una tarifa para transferir fondos desde su billetera Expensify a ' + electronicFundsInstantDetails: + 'Hay una tarifa para transferir fondos desde su billetera Expensify a ' + 'su tarjeta de débito vinculada utilizando la opción de transferencia instantánea. Esta transferencia ' + 'generalmente se completa dentro de varios minutos. La tarifa es el 1.5% del monto de la ' + 'transferencia (con una tarifa mínima de $ 0.25). ', - fdicInsuranceBancorp: 'Sus fondos son elegibles para el seguro de la FDIC. Sus fondos se mantendrán en o ' + fdicInsuranceBancorp: + 'Sus fondos son elegibles para el seguro de la FDIC. Sus fondos se mantendrán en o ' + 'transferido a The Bancorp Bank, una institución asegurada por la FDIC. Una vez allí, sus fondos ' + 'están asegurados a $ 250,000 por la FDIC en caso de que The Bancorp Bank quiebre. Ver', fdicInsuranceBancorp2: 'para detalles.', - contactExpensifyPayments: 'Comuníquese con Expensify Payments llamando al + 1833-400-0904, por correo' - + 'electrónico a', + contactExpensifyPayments: 'Comuníquese con Expensify Payments llamando al + 1833-400-0904, por correo' + 'electrónico a', contactExpensifyPayments2: 'o inicie sesión en', generalInformation: 'Para obtener información general sobre cuentas prepagas, visite', - generalInformation2: 'Si tiene una queja sobre una cuenta prepaga, llame al Consumer Financial Oficina de ' - + 'Protección al 1-855-411-2372 o visite', + generalInformation2: 'Si tiene una queja sobre una cuenta prepaga, llame al Consumer Financial Oficina de ' + 'Protección al 1-855-411-2372 o visite', printerFriendlyView: 'Ver versión para imprimir', automated: 'Automatizado', liveAgent: 'Agente en vivo', @@ -931,7 +938,8 @@ export default { letsChatText: 'Gracias. Necesitamos tu ayuda para verificar la información, pero podemos hacerlo rápidamente a través del chat. ¿Estás listo?', letsChatTitle: '¡Vamos a chatear!', enable2FATitle: 'Evita fraudes, activa la autenticación de dos factores!', - enable2FAText: 'Tu seguridad es importante para nosotros. Por favor, configura ahora la autenticación de dos factores. Eso nos permitirá disputar las transacciones de la Tarjeta Expensify y reducirá tu riesgo de fraude.', + enable2FAText: + 'Tu seguridad es importante para nosotros. Por favor, configura ahora la autenticación de dos factores. Eso nos permitirá disputar las transacciones de la Tarjeta Expensify y reducirá tu riesgo de fraude.', secureYourAccount: 'Asegura tu cuenta', }, beneficialOwnersStep: { @@ -1002,7 +1010,8 @@ export default { header: 'Desbloquea Tarjetas Expensify gratis', headerWithEcard: '¡Tus tarjetas están listas!', noVBACopy: 'Conecte una cuenta bancaria para emitir tarjetas Expensify a los miembros de su espacio de trabajo y acceda a estos increíbles beneficios y más:', - VBANoECardCopy: 'Agrega tu correo electrónico de trabajo para emitir Tarjetas Expensify ilimitadas para los miembros de tu espacio de trabajo y acceder a todas estas increíbles ventajas:', + VBANoECardCopy: + 'Agrega tu correo electrónico de trabajo para emitir Tarjetas Expensify ilimitadas para los miembros de tu espacio de trabajo y acceder a todas estas increíbles ventajas:', VBAWithECardCopy: 'Acceda a estos increíbles beneficios y más:', benefit1: 'Hasta un 4% de devolución en tus gastos', benefit2: 'Tarjetas digitales y físicas', @@ -1083,11 +1092,13 @@ export default { }, bankAccount: { continueWithSetup: 'Continuar con la configuración', - youreAlmostDone: 'Casi has acabado de configurar tu cuenta bancaria, que te permitirá emitir tarjetas corporativas, reembolsar gastos y cobrar pagar facturas, todo desde la misma cuenta bancaria.', + youreAlmostDone: + 'Casi has acabado de configurar tu cuenta bancaria, que te permitirá emitir tarjetas corporativas, reembolsar gastos y cobrar pagar facturas, todo desde la misma cuenta bancaria.', streamlinePayments: 'Optimiza pagos', oneMoreThing: '¡Una cosa más!', allSet: '¡Todo listo!', - accountDescriptionNoCards: 'Esta cuenta bancaria se utilizará para reembolsar gastos y cobrar y pagar facturas, todo desde la misma cuenta.\n\nPor favor, añade un correo electrónico de trabajo como tu nombre de usuario secundario para activar la Tarjeta Expensify.', + accountDescriptionNoCards: + 'Esta cuenta bancaria se utilizará para reembolsar gastos y cobrar y pagar facturas, todo desde la misma cuenta.\n\nPor favor, añade un correo electrónico de trabajo como tu nombre de usuario secundario para activar la Tarjeta Expensify.', accountDescriptionWithCards: 'Esta cuenta bancaria se utilizará para emitir tarjetas corporativas, reembolsar gastos y cobrar y pagar facturas, todo desde la misma cuenta.', addWorkEmail: 'Añadir correo electrónico de trabajo', letsFinishInChat: '¡Continuemos en el chat!', @@ -1154,12 +1165,14 @@ export default { newTaskPage: { task: 'Tarea', assignTask: 'Asignar tarea', + assignTo: 'A', title: 'Título', description: 'Descripción', shareIn: 'Compartir en', pleaseEnterTaskName: 'Por favor introduce un título', markAsComplete: 'Marcar como completa', markAsIncomplete: 'Marcar como incompleta', + pleaseEnterTaskAssignee: 'Por favor, asigna una persona a esta tarea', }, statementPage: { generatingPDF: 'Estamos generando tu PDF ahora mismo. ¡Por favor, vuelve más tarde!', diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 374d1e3248dc..4d9380984c04 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -1,3 +1,5 @@ +import CONST from '../../CONST'; +import ONYXKEYS from '../../ONYXKEYS'; import * as API from '../API'; import * as ReportUtils from '../ReportUtils'; import * as Report from './Report'; @@ -35,14 +37,30 @@ function assignTask(parentReportActionID, parentReportID, taskReportID, name, de Report.openReport(finalParentReportID, [assignee]); } - const optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(ownerEmail); - const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport(ownerEmail, assignee, finalParentReportID, parentReportActionID, name, description); + const optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(ownerEmail); // AddCommentReportAction on the parent chat report const optimisticAddCommentReportAction = ReportUtils.buildOptimisticAddCommentReportAction(finalParentReportID, optimisticTaskReport.reportID); - const optimisticData = [optimisticCreatedAction, optimisticTaskReport, optimisticAddCommentReportAction]; + const optimisticData = [ + { + method: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${finalParentReportID}`, + value: optimisticCreatedAction, + }, + { + method: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${finalParentReportID}`, + value: parentReport, + }, + { + method: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTaskReport.reportID}`, + value: optimisticTaskReport, + }, + optimisticAddCommentReportAction, + ]; const successData = {}; diff --git a/src/pages/NewTaskPage.js b/src/pages/NewTaskPage.js index a93025dfd5a6..59a96f0ecb74 100644 --- a/src/pages/NewTaskPage.js +++ b/src/pages/NewTaskPage.js @@ -2,7 +2,9 @@ import React from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import PropTypes from 'prop-types'; +import _ from 'underscore'; import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; + import compose from '../libs/compose'; import HeaderWithCloseButton from '../components/HeaderWithCloseButton'; import Navigation from '../libs/Navigation/Navigation'; @@ -13,6 +15,8 @@ import * as ErrorUtils from '../libs/ErrorUtils'; import Form from '../components/Form'; import TextInput from '../components/TextInput'; import Permissions from '../libs/Permissions'; +import OptionsSelector from '../components/OptionsSelector'; +import * as OptionsListUtils from '../libs/OptionsListUtils'; const propTypes = { /** List of betas available to current user */ @@ -26,6 +30,58 @@ const defaultProps = { // NOTE: This page is going to be updated in https://github.com/Expensify/App/issues/16855, this is just a placeholder for now const NewTaskPage = (props) => { + const [recentReports, setRecentReports] = React.useState([]); + const [personalDetails, setPersonalDetails] = React.useState([]); + const [userToInvite, setUserToInvite] = React.useState(null); + const [searchTerm, setSearchTerm] = React.useState(''); + const [assignee, setAssignee] = React.useState(null); + + /** + * @param {string} searchTerm + * @returs {Object} + */ + + function getRequestOptions(searchTerm = '') { + return OptionsListUtils.getNewChatOptions; + } + + /** + * Returns the sections needed for the OptionsSelector + * + * @returns {Array} + */ + function getSections() { + const sections = []; + let indexOffset = 0; + + sections.push({ + title: props.translate('common.recents'), + data: recentReports, + shouldShow: !_.isEmpty(recentReports), + indexOffset, + }); + indexOffset += recentReports.length; + + sections.push({ + title: props.translate('common.contacts'), + data: personalDetails, + shouldShow: !_.isEmpty(personalDetails), + indexOffset, + }); + indexOffset += personalDetails.length; + + if (userToInvite && !OptionsListUtils.isCurrentUser(userToInvite)) { + sections.push({ + undefined, + data: [userToInvite], + shouldShow: true, + indexOffset, + }); + } + + return sections; + } + /** * @param {Object} values - form input values passed by the Form component * @returns {Boolean} @@ -34,15 +90,22 @@ const NewTaskPage = (props) => { const errors = {}; if (!values.taskTitle) { - // We error if the user doesn't enter a room name + // We error if the user doesn't enter a task name ErrorUtils.addErrorMessage(errors, 'taskTitle', props.translate('newTaskPage.pleaseEnterTaskName')); } + if (!values.taskAssignee) { + // We error if the user doesn't enter a task assignee + ErrorUtils.addErrorMessage(errors, 'taskAssignee', props.translate('newTaskPage.pleaseEnterTaskAssignee')); + } + return errors; } - function onSubmit() { - + // On submit, we want to call the assignTask function and wait to validate + // the response + function onSubmit(values) { + console.log('submitting new task', values); } if (!Permissions.canUseTasks(props.betas)) { @@ -50,32 +113,38 @@ const NewTaskPage = (props) => { return null; } + const headerMessage = OptionsListUtils.getHeaderMessage(personalDetails.length + recentReports.length !== 0, Boolean(userToInvite), searchTerm); + return ( - Navigation.dismissModal()} - /> + Navigation.dismissModal()} />
validate(values)} - onSubmit={() => onSubmit()} + onSubmit={values => onSubmit(values)} enabledWhenOffline > - + + ; ; - + + + +
From 8d49bf9a154e77da1161c0f61f12094ef3a5794f Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Mon, 24 Apr 2023 23:51:15 -0400 Subject: [PATCH 06/87] Moved selector to its own page for assignee --- .../AppNavigator/ModalStackNavigators.js | 2 +- src/pages/NewTaskPage.js | 165 --------------- src/pages/tasks/AsigneeSelectorPage.js | 193 ++++++++++++++++++ src/pages/tasks/ChatSelectorPage.js | 0 src/pages/tasks/NewTaskPage.js | 99 +++++++++ 5 files changed, 293 insertions(+), 166 deletions(-) delete mode 100644 src/pages/NewTaskPage.js create mode 100644 src/pages/tasks/AsigneeSelectorPage.js create mode 100644 src/pages/tasks/ChatSelectorPage.js create mode 100644 src/pages/tasks/NewTaskPage.js diff --git a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js index 3b58d67a2670..587bf167cb10 100644 --- a/src/libs/Navigation/AppNavigator/ModalStackNavigators.js +++ b/src/libs/Navigation/AppNavigator/ModalStackNavigators.js @@ -220,7 +220,7 @@ const NewChatModalStackNavigator = createModalStackNavigator([{ const NewTaskModalStackNavigator = createModalStackNavigator([{ getComponent: () => { - const NewTaskPage = require('../../../pages/NewTaskPage').default; + const NewTaskPage = require('../../../pages/tasks/NewTaskPage').default; return NewTaskPage; }, name: 'NewTask_Root', diff --git a/src/pages/NewTaskPage.js b/src/pages/NewTaskPage.js deleted file mode 100644 index 59a96f0ecb74..000000000000 --- a/src/pages/NewTaskPage.js +++ /dev/null @@ -1,165 +0,0 @@ -import React from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; - -import compose from '../libs/compose'; -import HeaderWithCloseButton from '../components/HeaderWithCloseButton'; -import Navigation from '../libs/Navigation/Navigation'; -import ScreenWrapper from '../components/ScreenWrapper'; -import styles from '../styles/styles'; -import ONYXKEYS from '../ONYXKEYS'; -import * as ErrorUtils from '../libs/ErrorUtils'; -import Form from '../components/Form'; -import TextInput from '../components/TextInput'; -import Permissions from '../libs/Permissions'; -import OptionsSelector from '../components/OptionsSelector'; -import * as OptionsListUtils from '../libs/OptionsListUtils'; - -const propTypes = { - /** List of betas available to current user */ - betas: PropTypes.arrayOf(PropTypes.string), - - ...withLocalizePropTypes, -}; -const defaultProps = { - betas: [], -}; - -// NOTE: This page is going to be updated in https://github.com/Expensify/App/issues/16855, this is just a placeholder for now -const NewTaskPage = (props) => { - const [recentReports, setRecentReports] = React.useState([]); - const [personalDetails, setPersonalDetails] = React.useState([]); - const [userToInvite, setUserToInvite] = React.useState(null); - const [searchTerm, setSearchTerm] = React.useState(''); - const [assignee, setAssignee] = React.useState(null); - - /** - * @param {string} searchTerm - * @returs {Object} - */ - - function getRequestOptions(searchTerm = '') { - return OptionsListUtils.getNewChatOptions; - } - - /** - * Returns the sections needed for the OptionsSelector - * - * @returns {Array} - */ - function getSections() { - const sections = []; - let indexOffset = 0; - - sections.push({ - title: props.translate('common.recents'), - data: recentReports, - shouldShow: !_.isEmpty(recentReports), - indexOffset, - }); - indexOffset += recentReports.length; - - sections.push({ - title: props.translate('common.contacts'), - data: personalDetails, - shouldShow: !_.isEmpty(personalDetails), - indexOffset, - }); - indexOffset += personalDetails.length; - - if (userToInvite && !OptionsListUtils.isCurrentUser(userToInvite)) { - sections.push({ - undefined, - data: [userToInvite], - shouldShow: true, - indexOffset, - }); - } - - return sections; - } - - /** - * @param {Object} values - form input values passed by the Form component - * @returns {Boolean} - */ - function validate(values) { - const errors = {}; - - if (!values.taskTitle) { - // We error if the user doesn't enter a task name - ErrorUtils.addErrorMessage(errors, 'taskTitle', props.translate('newTaskPage.pleaseEnterTaskName')); - } - - if (!values.taskAssignee) { - // We error if the user doesn't enter a task assignee - ErrorUtils.addErrorMessage(errors, 'taskAssignee', props.translate('newTaskPage.pleaseEnterTaskAssignee')); - } - - return errors; - } - - // On submit, we want to call the assignTask function and wait to validate - // the response - function onSubmit(values) { - console.log('submitting new task', values); - } - - if (!Permissions.canUseTasks(props.betas)) { - Navigation.dismissModal(); - return null; - } - - const headerMessage = OptionsListUtils.getHeaderMessage(personalDetails.length + recentReports.length !== 0, Boolean(userToInvite), searchTerm); - - return ( - - Navigation.dismissModal()} /> -
validate(values)} - onSubmit={values => onSubmit(values)} - enabledWhenOffline - > - - - - ; ; - - - - - - - -
-
- ); -}; - -NewTaskPage.displayName = 'NewTaskPage'; -NewTaskPage.propTypes = propTypes; -NewTaskPage.defaultProps = defaultProps; - -export default compose( - withOnyx({ - betas: { - key: ONYXKEYS.BETAS, - }, - }), - withLocalize, -)(NewTaskPage); diff --git a/src/pages/tasks/AsigneeSelectorPage.js b/src/pages/tasks/AsigneeSelectorPage.js new file mode 100644 index 000000000000..d4e64f3294fd --- /dev/null +++ b/src/pages/tasks/AsigneeSelectorPage.js @@ -0,0 +1,193 @@ +import _ from 'underscore'; +import React, {Component} from 'react'; +import {View} from 'react-native'; +import PropTypes from 'prop-types'; +import {withOnyx} from 'react-native-onyx'; +import OptionsSelector from '../components/OptionsSelector'; +import * as OptionsListUtils from '../libs/OptionsListUtils'; +import ONYXKEYS from '../ONYXKEYS'; +import styles from '../styles/styles'; +import Navigation from '../libs/Navigation/Navigation'; +import ROUTES from '../ROUTES'; +import withWindowDimensions, {windowDimensionsPropTypes} from '../components/withWindowDimensions'; +import * as Report from '../libs/actions/Report'; +import HeaderWithCloseButton from '../components/HeaderWithCloseButton'; +import ScreenWrapper from '../components/ScreenWrapper'; +import Timing from '../libs/actions/Timing'; +import CONST from '../CONST'; +import withLocalize, {withLocalizePropTypes} from '../components/withLocalize'; +import compose from '../libs/compose'; +import personalDetailsPropType from './personalDetailsPropType'; +import reportPropTypes from './reportPropTypes'; +import Performance from '../libs/Performance'; + +const propTypes = { + /* Onyx Props */ + + /** Beta features list */ + betas: PropTypes.arrayOf(PropTypes.string), + + /** All of the personal details for everyone */ + personalDetails: personalDetailsPropType, + + /** All reports shared with the user */ + reports: PropTypes.objectOf(reportPropTypes), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + betas: [], + personalDetails: {}, + reports: {}, +}; + + +const AssigneeSelectorPage = (props) => { + const [searchValue, setSearchValue] = useState(''); + const [headerMessage, setHeaderMessage] = useState(''); + const [recentReports, setRecentReports] = useState([]); + const [personalDetails, setPersonalDetails] = useState([]); + const [userToInvite, setUserToInvite] = useState(null); + + useEffect(() => { + Timing.start(CONST.TIMING.SEARCH_RENDER); + Performance.markStart(CONST.TIMING.SEARCH_RENDER); + + updateOptions(); + + return () => { + Timing.end(CONST.TIMING.SEARCH_RENDER); + Performance.markEnd(CONST.TIMING.SEARCH_RENDER); + }; + }, []); + + const onChangeText = (searchValue = '') => { + setSearchValue(searchValue); + debouncedUpdateOptions(); + }; + + const debouncedUpdateOptions = _.debounce(updateOptions, 75); + + const updateOptions = () => { + const {recentReports, personalDetails, userToInvite} = + OptionsListUtils.getSearchOptions( + props.reports, + props.personalDetails, + searchValue.trim(), + props.betas, + ); + + setHeaderMessage( + OptionsListUtils.getHeaderMessage( + recentReports.length + personalDetails.length !== 0, + Boolean(userToInvite), + searchValue, + ), + ); + + setUserToInvite(userToInvite); + setRecentReports(recentReports); + setPersonalDetails(personalDetails); + }; + + const getSections = () => { + const sections = []; + let indexOffset = 0; + + if (recentReports.length > 0) { + sections.push({ + data: recentReports, + shouldShow: true, + indexOffset, + }); + indexOffset += recentReports.length; + } + + if (personalDetails.length > 0) { + sections.push({ + data: personalDetails, + shouldShow: true, + indexOffset, + }); + indexOffset += recentReports.length; + } + + if (userToInvite) { + sections.push({ + data: [userToInvite], + shouldShow: true, + indexOffset, + }); + } + + return sections; + }; + + const selectReport = (option) => { + if (!option) { + return; + } + + if (option.reportID) { + setSearchValue(''); + Navigation.navigate(ROUTES.getReportRoute(option.reportID)); + } else { + Report.navigateToAndOpenReport([option.login]); + } + }; + + const sections = getSections(); + return ( + + {({didScreenTransitionEnd, safeAreaPaddingBottomStyle}) => ( + <> + Navigation.dismissModal(true)} + /> + + { + Timing.end(CONST.TIMING.SEARCH_RENDER); + Performance.markEnd(CONST.TIMING.SEARCH_RENDER); + }} + safeAreaPaddingBottomStyle={safeAreaPaddingBottomStyle} + /> + + + )} + + ); +}; + +SearchPage.propTypes = propTypes; +SearchPage.defaultProps = defaultProps; + +export default compose( + withLocalize, + withWindowDimensions, + withOnyx({ + reports: { + key: ONYXKEYS.COLLECTION.REPORT, + }, + personalDetails: { + key: ONYXKEYS.PERSONAL_DETAILS, + }, + betas: { + key: ONYXKEYS.BETAS, + }, + }), +)(SearchPage); diff --git a/src/pages/tasks/ChatSelectorPage.js b/src/pages/tasks/ChatSelectorPage.js new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/pages/tasks/NewTaskPage.js b/src/pages/tasks/NewTaskPage.js new file mode 100644 index 000000000000..a68f4cfc91a4 --- /dev/null +++ b/src/pages/tasks/NewTaskPage.js @@ -0,0 +1,99 @@ +import React from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import PropTypes from 'prop-types'; +import withLocalize, {withLocalizePropTypes} from '../../components/withLocalize'; +import compose from '../../libs/compose'; +import HeaderWithCloseButton from '../../components/HeaderWithCloseButton'; +import Navigation from '../../libs/Navigation/Navigation'; +import ScreenWrapper from '../../components/ScreenWrapper'; +import styles from '../../styles/styles'; +import ONYXKEYS from '../../ONYXKEYS'; +import * as ErrorUtils from '../../libs/ErrorUtils'; +import Form from '../../components/Form'; +import TextInput from '../../components/TextInput'; +import Permissions from '../../libs/Permissions'; +import Button from '../../components/Button'; + +const propTypes = { + /** Beta features list */ + betas: PropTypes.arrayOf(PropTypes.string), + + ...withLocalizePropTypes, +}; + +const defaultProps = { + betas: [], +}; + +// NOTE: This page is going to be updated in https://github.com/Expensify/App/issues/16855, this is just a placeholder for now +const NewTaskPage = (props) => { + const [assignee, setAssignee] = React.useState(null); + + /** + * @param {Object} values - form input values passed by the Form component + * @returns {Boolean} + */ + function validate(values) { + const errors = {}; + + if (!values.taskTitle) { + // We error if the user doesn't enter a task name + ErrorUtils.addErrorMessage(errors, 'taskTitle', props.translate('newTaskPage.pleaseEnterTaskName')); + } + + if (!values.taskAssignee) { + // We error if the user doesn't enter a task assignee + ErrorUtils.addErrorMessage(errors, 'taskAssignee', props.translate('newTaskPage.pleaseEnterTaskAssignee')); + } + + return errors; + } + + // On submit, we want to call the assignTask function and wait to validate + // the response + function onSubmit(values) { + console.log('submitting new task', values); + } + + if (!Permissions.canUseTasks(props.betas)) { + Navigation.dismissModal(); + return null; + } + return ( + + Navigation.dismissModal()} /> +
validate(values)} + onSubmit={values => onSubmit(values)} + enabledWhenOffline + > + +