Skip to content

Migrate per diem subrate step to dynamic routes (#83850 part 3) - #98629

Open
suneox wants to merge 6 commits into
Expensify:mainfrom
suneox:83850-migrate-money-request-part-3-v2-suneox
Open

Migrate per diem subrate step to dynamic routes (#83850 part 3)#98629
suneox wants to merge 6 commits into
Expensify:mainfrom
suneox:83850-migrate-money-request-part-3-v2-suneox

Conversation

@suneox

@suneox suneox commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Explanation of Change

This is part 3 of migrating the per diem money request steps to dynamic URL navigation (after part 1 — destination and part 2 — time). It replaces the static MONEY_REQUEST_STEP_SUBRATE and MONEY_REQUEST_STEP_SUBRATE_EDIT routes with the per-diem-subrate/:pageIndex and per-diem-subrate-edit/:pageIndex dynamic suffixes. The wizard suffix is stacked on the dynamic time route (entryScreens: [MONEY_REQUEST.DYNAMIC_STEP_TIME]) and the edit suffix on the confirmation route (entryScreens: [MONEY_REQUEST.STEP_CONFIRMATION]), so both drop backTo and derive their back path from the current URL through useDynamicBackPath; the page distinguishes edit mode by route name instead of by backTo.

IOURequestStepSubrate is renamed to DynamicIOURequestStepSubrate, and PerDiemFields no longer needs the action / iouType / reportID props now that all three per diem fields navigate through dynamic routes. Legacy subrate URLs (including the optional backToReport segment and the edit variant) are redirected through OldRoutes, with unit tests covering both the redirect shape and the no-false-positive case for the already-migrated paths.

Fixed Issues

$ #83850
PROPOSAL:

Tests

DYNAMIC_ROUTES.MONEY_REQUEST_STEP_SUBRATE — wizard subrate step

Case 1.1 — Time → Subrate forward (multi-policy base)

Target: src/pages/iou/request/step/DynamicIOURequestStepTime.tsx (updateTime)

Precondition: the account has 2 or more workspaces with per diem enabled (so the flow goes through the workspace selector → Destination → Time).

Steps:

  1. Open the app and sign in.
  2. Tap the FAB (+)Create expensePer diem tab.
  3. Pick a workspace → pick a Destination → on the Time step enter start/end date-time → Save.

Expected: the Subrate step opens (Subrate picker + Quantity input), the URL ends with /per-diem-time/per-diem-subrate/0, and the header title is the tab title (not "Subrate").


Case 1.2 — Time → Subrate forward (single-policy base, opened from the start page)

Precondition: the account has exactly one workspace with per diem enabled (Destination renders inline on the start page, so Time is built on the start base).

Steps:

  1. FAB (+) → Create expensePer diem tab.
  2. Pick the destination on the start page → Time step → enter date-time → Save.

Expected: Subrate opens, URL is /create/submit/start/<txn>/<rpt>/per-diem-time/per-diem-subrate/0 (no per-diem-destination segment).


Case 1.3 — Subrate back → Time

Target: src/pages/iou/request/step/DynamicIOURequestStepSubrate.tsx

Steps:

  1. From case 1.1 (on Subrate), tap Back in the header.

Expected: back on the Time step; exactly the /per-diem-subrate/0 segment is removed from the URL and the base is unchanged. No /not-found.


Case 1.4 — Subrate submit → Confirmation

Steps:

  1. On Subrate, pick a Subrate and enter a Quantity (e.g. 2) → Save.

Expected: the Confirmation step opens (still a static route) and the subrate shows up in the per diem fields.


Case 1.5 — Confirmation back → Subrate

Target: src/pages/iou/request/step/IOURequestStepConfirmation.tsx (navigateBack)

Steps:

  1. From case 1.4 (Confirmation), tap Back.

Expected: back on Subrate (the route is already on the stack, so it pops to it). No /not-found and no duplicated suffix.


Case 1.6 — Refresh while on Subrate

Steps:

  1. From case 1.1, refresh the browser (F5).

Expected: still on the Subrate step and the stack is restored (Back goes to Time).


Case 1.7 — Legacy deep link redirect (pageIndex preserved)

Target: src/libs/Navigation/linkingConfig/OldRoutes.ts

Steps:

  1. Paste the legacy URL: /create/submit/subrate/<txn>/<rpt>/0.

Expected: redirected to /create/submit/start/<txn>/<rpt>/per-diem-time/per-diem-subrate/0. ⚠️ getMatchingNewRoute re-appends the original query, so a legacy URL that still carries ?backTo= keeps it on the redirected route (and on its base route). This matches the destination and time redirects already on main; no call site emits that query anymore.


Case 1.8 — Legacy deep link with a backToReport segment

Steps:

  1. Paste /create/submit/subrate/<txn>/<rpt>/<backToReportID>/2.

Expected: redirected to /create/submit/start/<txn>/<rpt>/<backToReportID>/per-diem-time/per-diem-subrate/2.


Case 1.9 — Offline forward/back

Steps:

  1. Go offline (DevTools → Network → Offline).
  2. Repeat cases 1.1 → 1.3 → 1.4.

Expected: navigation still works offline and the offline indicator shows.



DYNAMIC_ROUTES.MONEY_REQUEST_STEP_SUBRATE_EDIT — edit a subrate from Confirmation

Case 2.1 — Confirmation → edit Subrate (correct index)

Target: src/components/MoneyRequestConfirmationList/sections/PerDiemFields.tsx

Precondition: a per diem Confirmation with at least one subrate (run case 1.4 first).

Steps:

  1. On Confirmation, tap the first Subrate field (index 0).

Expected: the Subrate page opens in edit mode, header title "Subrate", URL /create/submit/confirmation/<txn>/<rpt>/per-diem-subrate-edit/0, and the picker/quantity are prefilled with subrate 0.


Case 2.2 — Edit save → back on Confirmation

Steps:

  1. From case 2.1, change the Quantity (e.g. 23) → Save.

Expected: back on Confirmation (the /per-diem-subrate-edit/0 segment is removed) and the field shows the new quantity.


Case 2.3 — Edit back without saving

Steps:

  1. From case 2.1, tap Back without saving.

Expected: back on Confirmation with the subrate unchanged.


Case 2.4 — Edit the second subrate (pageIndex > 0)

Precondition: the rate has 2 or more subrates and the expense already has 2 subrates.

Steps:

  1. On Confirmation, tap the second Subrate field.

Expected: URL .../per-diem-subrate-edit/1 and subrate index 1 is shown (not index 0).


Case 2.5 — Delete a subrate (three-dots menu)

Steps:

  1. From case 2.4 (editing subrate 1, more than one subrate present), tap the three-dots menu → Delete subrate → confirm.

Expected: the subrate is deleted and the app goes back to Confirmation with the remaining subrates listed.


Case 2.6 — Legacy edit deep link redirect

Steps:

  1. Paste /create/submit/subrate/<txn>/<rpt>/edit/2.

Expected: redirected to /create/submit/confirmation/<txn>/<rpt>/per-diem-subrate-edit/2.



Regression
  • Verify that no errors appear in the JS console

Offline tests

  • Same as tests — navigation refactor, offline behavior unchanged.

QA Steps

Test case 1: Create a per diem expense and reach the subrate step

  1. Sign in with an account that has a workspace with per diem enabled (with rates that have at least two subrates).
  2. Tap the green + button → Create expense → open the Per diem tab.
  3. Pick the workspace (if you are asked to) and pick a destination.
  4. Enter a start and end date-time and tap Save.
  5. Verify the Subrate page opens with a subrate picker and a quantity field, and that the browser URL ends with /per-diem-time/per-diem-subrate/0.
  6. Tap Back and verify you return to the time step, with the /per-diem-subrate/0 part removed from the URL.

Test case 2: Edit a subrate from the confirmation page

  1. Continue from test case 1: on the subrate page pick a subrate, enter a quantity and tap Save.
  2. Verify the confirmation page opens and shows the subrate you just entered.
  3. Tap the Subrate field on the confirmation page.
  4. Verify the subrate page opens with the header title Subrate, the values prefilled, and the browser URL ending with /per-diem-subrate-edit/0.
  5. Change the quantity and tap Save.
  6. Verify you are back on the confirmation page and the subrate field shows the new quantity.
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If new assets were added or existing ones were modified, I verified that:
    • The assets are optimized and compressed (for SVG files, run npm run compress-svg)
    • The assets load correctly across all supported platforms.
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • I added unit tests for any new feature or bug fix in this PR to help automatically prevent regressions in this user flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari
CleanShot.2026-08-17.at.14.09.11.1.mp4

+++ Case 1.1 — Time → Subrate forward (multi-policy base)

1 1-1-before-time-step-multipolicy 1 1-2-entry-subrate-multipolicy

+++

+++ Case 1.2 — Time → Subrate forward (single-policy base)

1 2-1-before-time-step 1 2-2-entry-subrate-dynamic

+++

+++ Case 1.3 — Subrate back → Time

1 3-1-before-subrate-step 1 3-2-entry-time-step

+++

+++ Case 1.4 — Subrate submit → Confirmation

1 4-1-before-subrate-filled 1 4-2-entry-confirmation-step

+++

+++ Case 1.5 — Confirmation back → Subrate

1 5-1-before-confirmation-step 1 5-2-entry-subrate-popped

+++

+++ Case 1.6 — Refresh while on Subrate

1 6-1-before-subrate-before-refresh 1 6-2-entry-after-refresh 1 6-3-after-back-time-restored

+++

+++ Case 1.7 — Legacy deep link redirect

1 7-1-before-nonedit-oldurl 1 7-2-entry-nonedit-redirected

+++

+++ Case 1.8 — Legacy deep link with backToReport

Not support create deeplink with subrate

+++

+++ Case 1.9 — Offline forward/back

1 9-1-before-destination-offline 1 9-2-entry-offline-confirmation

+++

+++ Case 2.1 — Confirmation → edit Subrate

2 1-1-before-confirmation-subrate-row 2 1-2-entry-subrate-edit

+++

+++ Case 2.2 — Edit save → back on Confirmation

2 2-1-before-edit-qty3 2 2-2-entry-confirmation-qty3

+++

+++ Case 2.3 — Edit back without saving

2 3-1-before-edit-before-back 2 3-2-entry-confirmation-unchanged

+++

+++ Case 2.4 — Edit the second subrate

2 4-1-before-confirmation-lunch-row 2 4-2-entry-subrate-edit-1-lunch

+++

+++ Case 2.5 — Delete a subrate

2 5-1-before-edit-lunch 2 5-3-after-back-confirmation-lunch-deleted

+++

+++ Case 2.6 — Legacy edit deep link redirect

Not support create via deep link with edit subrate

+++

+++ Regression

R1-1-before-confirmation-dest-row R1-2-entry-destination-edit R2-1-before-confirmation-time-row R2-2-entry-time-edit R3-1-before-time-step-back-check R3-2-entry-destination-no-double

+++

@suneox
suneox requested review from a team as code owners August 14, 2026 04:36
@melvin-bot
melvin-bot Bot requested review from JmillsExpensify and huult and removed request for a team August 14, 2026 04:36
@melvin-bot

melvin-bot Bot commented Aug 14, 2026

Copy link
Copy Markdown

@huult Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@suneox
suneox marked this pull request as draft August 14, 2026 04:37
Comment thread src/libs/Navigation/linkingConfig/OldRoutes.ts Outdated
Comment thread src/pages/iou/request/step/DynamicIOURequestStepSubrate.tsx Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 14e4d0067f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/libs/Navigation/linkingConfig/OldRoutes.ts
@suneox
suneox force-pushed the 83850-migrate-money-request-part-3-v2-suneox branch from 14e4d00 to 4b3c9c1 Compare August 14, 2026 05:03
@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

@JmillsExpensify
JmillsExpensify removed their request for review August 14, 2026 08:41

@JmillsExpensify JmillsExpensify left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No product review required.

@huult

huult commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Tag me when it's ready

suneox added 2 commits August 17, 2026 12:18
…BRATE_EDIT routes with the and dynamic suffixes, following parts 1 (destination) and 2 (time).

- The wizard suffix is stacked on the dynamic time route, the edit suffix on the
  confirmation route, so both drop  and derive their back path from the URL
  via useDynamicBackPath.
- Rename IOURequestStepSubrate to DynamicIOURequestStepSubrate and switch the
  edit-mode check from  to the route name.
- PerDiemFields no longer needs action/iouType/reportID now that all three per diem
  fields navigate through dynamic routes.
- Add OldRoutes redirects (including the legacy backToReport variant) plus unit tests.
@suneox
suneox force-pushed the 83850-migrate-money-request-part-3-v2-suneox branch from 4b3c9c1 to ee9023a Compare August 17, 2026 07:27
@suneox
suneox marked this pull request as ready for review August 17, 2026 07:37
@suneox

suneox commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

Tag me when it's ready

It's ready

@huult

huult commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified that the composer does not automatically focus or open the keyboard on mobile unless explicitly intended. This includes checking that returning the app from the background does not unexpectedly open the keyboard.
  • I verified tests pass on all platforms & I tested again on:
    • Android: HybridApp
    • Android: mWeb Chrome
    • iOS: HybridApp
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design so the design team can review the changes.
  • For any bug fix or new feature in this PR, I verified that sufficient unit tests are included to prevent regressions in this flow.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: HybridApp
Android: mWeb Chrome
iOS: HybridApp
iOS: mWeb Safari
MacOS: Chrome / Safari

Comment thread src/libs/Navigation/linkingConfig/OldRoutes.ts Outdated
Comment thread src/pages/iou/request/step/DynamicIOURequestStepSubrate.tsx Outdated
Comment thread src/pages/iou/request/step/DynamicIOURequestStepTime.tsx Outdated
Comment thread src/pages/iou/request/step/IOURequestStepConfirmation.tsx Outdated
@huult

huult commented Aug 17, 2026

Copy link
Copy Markdown
Contributor
Screen.Recording.2026-08-17.at.15.32.06.mov
Screen.Recording.2026-08-17.at.15.35.18.mov

BUG: Not Found appears when opening the Per Diem subrate

@suneox

suneox commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

BUG: Not Found appears when opening the Per Diem subrate

@huult Could you check your build and confirm if the bundle is ready? I haven't encountered any bugs.

CleanShot.2026-08-17.at.15.37.57.1.mp4

@huult

huult commented Aug 17, 2026

Copy link
Copy Markdown
Contributor
Screen.Recording.2026-08-17.at.15.44.29.mov

@suneox I’m still able to reproduce this issue

@suneox

suneox commented Aug 17, 2026

Copy link
Copy Markdown
Contributor Author

@huult The issue has been fixed for both single and multiple workspaces with per-diem enabled

CleanShot.2026-08-17.at.17.26.10.1.mp4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants