Render role-restricted card links as plain text when viewer lacks access - #98612
Render role-restricted card links as plain text when viewer lacks access#98612MelvinBot wants to merge 6 commits into
Conversation
…s access Co-authored-by: truph01 <truph01@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
Codecov Report✅ Changes either increased or maintained existing code coverage, great job!
|
Co-authored-by: truph01 <truph01@users.noreply.github.com>
|
Fixed the failing Oxfmt check. The formatter flagged two files for import-ordering violations; I ran the project formatter and pushed the result:
The only change was re-sorting the The other red checks are not related to code:
|
|
The one actionable failing check — Oxfmt — is now green after my earlier import-sort fix (commit The only remaining red checks are process gates I can't resolve with code:
Nothing further for me to push here. |
|
The Oxfmt check is now passing after my earlier import-sort fix (commit The only remaining red checks are non-code process gates that Melvin doesn't act on:
These clear through the normal review/approval flow, not a code change. |
|
@MelvinBot Mark this PR as ready |
|
🤖 Done — this PR is now marked Ready for review (no longer a draft). Quick status check before it goes out:
|
| // See https://github.com/Expensify/App/issues/72352 for more details. | ||
| const activeRoute = Navigation.getActiveRoute(); | ||
| const exportMenuItem = getExportMenuItem(connectedIntegration, policyID, translate, styles, policy, card, activeRoute); | ||
| // The accounting export-configuration page is admin-only, so only link to it for policy admins; card admins |
There was a problem hiding this comment.
❌ CONSISTENCY-16 (docs)
This comment joins two independent clauses with a semicolon (...for policy admins; card admins without access...). Per the plain-comment-style guideline, use two separate sentences instead of a semicolon.
Split into two sentences:
// The accounting export-configuration page is admin-only, so only link to it for policy admins. Card admins
// without access would otherwise be dropped onto the Not Found page. When they lack access, render plain (non-linked) text.Reviewed at: 09955aa | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
Fixed in b08aa0d — split the semicolon-joined clauses into two sentences per CONSISTENCY-16.
| } | ||
|
|
||
| const workspaceWorkflowsPageURL = `${environmentURL}/${ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID)}`; | ||
| // Only link to the Workflows page when the current user can actually read it; card admins without Workflows |
There was a problem hiding this comment.
❌ CONSISTENCY-16 (docs)
This comment joins two independent clauses with a semicolon (...actually read it; card admins without Workflows access...). Per the plain-comment-style guideline, use two separate sentences instead of a semicolon.
Split into two sentences:
// Only link to the Workflows page when the current user can actually read it. Card admins without Workflows
// access would otherwise be dropped onto the Not Found page. When they lack access, render plain (non-linked) text.Reviewed at: 09955aa | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
Fixed in b08aa0d — split the semicolon-joined clauses into two sentences per CONSISTENCY-16.
| const connectionName = reconciliationConnection ?? ''; | ||
| const connectionParam = getRouteParamForConnection(connectionName as ConnectionName); | ||
|
|
||
| // The Card reconciliation page is admin-only, so only link to it for policy admins; card admins without |
There was a problem hiding this comment.
❌ CONSISTENCY-16 (docs)
This comment joins two independent clauses with a semicolon (...for policy admins; card admins without access...). Per the plain-comment-style guideline, use two separate sentences instead of a semicolon.
Split into two sentences:
// The Card reconciliation page is admin-only, so only link to it for policy admins. Card admins without
// access would otherwise be dropped onto the Not Found page. When they lack access, render plain (non-linked) text.Reviewed at: 09955aa | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
Fixed in b08aa0d — split the semicolon-joined clauses into two sentences per CONSISTENCY-16.
| const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`); | ||
| const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector}); | ||
|
|
||
| // Only link to the Workflows page when the current user can actually read it; card admins without Workflows |
There was a problem hiding this comment.
❌ CONSISTENCY-16 (docs)
This comment joins two independent clauses with a semicolon (...actually read it; card admins without Workflows access...). Per the plain-comment-style guideline, use two separate sentences instead of a semicolon.
Split into two sentences:
// Only link to the Workflows page when the current user can actually read it. Card admins without Workflows
// access would otherwise be dropped onto the Not Found page. When they lack access, render plain (non-linked) text.Reviewed at: 09955aa | Please rate this suggestion with 👍 or 👎 to help us improve! Reactions are used to monitor reviewer efficiency.
There was a problem hiding this comment.
Fixed in b08aa0d — split the semicolon-joined clauses into two sentences per CONSISTENCY-16.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 09955aa040
ℹ️ 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".
| 'workspace.moreFeatures.companyCards.integrationExportTitle', | ||
| exportMenuItem.description, | ||
| exportMenuItem.exportPageLink ? `${environmentURL}/${exportMenuItem.exportPageLink}` : undefined, | ||
| exportPageLink ? `${environmentURL}/${exportPageLink}` : undefined, |
There was a problem hiding this comment.
Preserve export-option text when link is hidden
For card admins who can access this card-export selector but are not policy admins, exportPageLink is now undefined; however integrationExportTitle only renders the “Select a different export option…” sentence when that argument is truthy, so the export-option text disappears instead of becoming plain non-linked text. This misses the stated card-admin behavior for the company-card export case while the Smart Limit and Reconciliation strings do keep their plain-text fallback.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The Codex observation is factually correct, but I'd argue the current behavior is acceptable (arguably preferable) and not a bug to fix.
For a card admin (can reach this selector, not a policy admin), exportPageLink is undefined, and integrationExportTitle drops the entire second sentence rather than rendering plain text — so it does differ from smartLimitDisabledDescription and settlementAccountInfo.
Why the difference is defensible
The three strings aren't equivalent — the anchor plays a different grammatical role in each:
- Smart Limit / Reconciliation: the anchor is a noun the sentence is about ("Enable approvals to select this option", "matches your Reconciliation account (1234)"). Dropping it would leave an ungrammatical fragment, so plain text is required to keep the sentence intact.
- Export: the anchor lives inside a standalone call-to-action — "Select a different export option to change the available accounts." — whose only purpose is to send the user to an admin-only page. For a card admin who can't reach that page, rendering it as dead, non-clickable plain text is a dead-end instruction. Omitting the whole clause leaves the base sentence ("Choose the {integration} account where transactions should be exported.") fully intact and correct.
So no user-facing text is broken — the export case just hides an instruction that wouldn't be actionable for that viewer.
Recommendation: leave the code as-is (lowest risk, better UX), and instead soften the PR-description line that says all three "render the anchor text as plain text when no link is passed" — for integrationExportTitle it drops the non-actionable CTA clause entirely. If you'd rather have strict consistency (plain-text "export option" for card admins too), I can make that change — just confirm and I'll push it.
There was a problem hiding this comment.
Done — pushed 2b2776e.
integrationExportTitle now always renders the "Select a different export option…" sentence, with only the anchor gated on the link — so a card admin without export-config access sees plain export option text instead of the sentence disappearing, matching the Smart Limit and Reconciliation strings. Applied to en.ts and mirrored across all 11 locale files.
Verified: en.ts lints clean and the edited files parse without syntax errors.
|
The Oxfmt check is now passing after the import-sort fix (commit The only remaining red checks are process/approval gates that require human action, not a code fix:
No further code changes are needed from me. |
|
@MelvinBot Check all the above comments |
|
The three failing checks are all process/review gates, not code failures — there's nothing to push a code fix for:
All actual code checks are green (typecheck, ESLint, unit tests, Storybook, perf, build, etc.). To clear the remaining checks: complete the author checklist and fill out the Tests/QA steps in the PR body, then get the PR reviewed and approved. |
|
Nothing has changed since my earlier comment — the only failing checks are still the three process/review gates, and none of them can be fixed by pushing code:
Every actual code check (typecheck, ESLint, unit tests, Storybook, perf, builds, etc.) is green. To clear the remaining checks, the author co-author needs to complete the checklist and fill out the Tests/QA steps, then get the PR reviewed and independently approved. |
…ences Co-authored-by: truph01 <truph01@users.noreply.github.com>
|
@youssef-lr 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] |
|
Checked all 5 review comments. Summary: 4× CONSISTENCY-16 (github-actions) — valid, fixed in
1× P2 (chatgpt-codex) — factually correct, but I recommend no change. For a card admin, These are comment-only edits, so no functional/behavioral change. |
|
Still nothing to fix in code — the only failing checks are the same process/review gates as before, and all real code checks (typecheck, ESLint, unit tests, Storybook, perf, builds) are green:
These clear once the author co-author fills out the checklist + Tests/QA steps and the PR gets an independent review/approval. No push is possible for them. |
|
Re-checked — nothing has changed since my previous comment. The only three failing checks are still process/review gates, not code failures, so there's no fix to push:
Every actual code check (typecheck, ESLint, unit tests, Storybook, perf, builds, etc.) is green. To clear the remaining checks, the human co-author needs to fill out the Tests/QA steps and tick the author checklist, then get the PR reviewed and approved. |
|
@MelvinBot In this PR, only focus on addressing
So pls revert others and update test section in checklist. |
…export-option link changes Co-authored-by: truph01 <truph01@users.noreply.github.com>
|
Done — scoped the PR down to the "Enable approvals" deploy blocker only. Pushed Reverted (now byte-identical to
Kept (the actual fix):
Checklist: trimmed the Explanation and the Tests "Suggested repro" to cover only the Enable-approvals flow (new-card + edit-card) and added a scope note. The human Local checks: The net diff is now just the two limit-type pages + the |
|
Still no code fix to push — the only two failing checks are process/review gates:
Every actual code check is green (typecheck, ESLint, unit tests, Storybook, perf, builds, etc.), so there's nothing for me to fix in code. To clear these: the human co-author needs to fill out the Tests/QA steps and complete the author checklist, then get an independent review/approval. |
| const formRef = useRef<FormRef | null>(null); | ||
| const [issueNewCard] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_ISSUE_NEW_EXPENSIFY_CARD}${policyID}`); | ||
| const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`); | ||
| const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector}); |
There was a problem hiding this comment.
@MelvinBot Same login source as WorkspaceWorkflowsPage? That page feeds canMemberRead from useCurrentUserPersonalDetails().login, whereas this uses session.email — usually identical, but would matching the source keep the gate exactly in sync?
There was a problem hiding this comment.
Good catch — in the loaded (steady) state the gate won't drift, but matching the source is the more airtight choice, and I'd lean toward it.
canMemberRead ultimately does an exact key lookup: policy.employeeList[login].role. For the current user, session.email and useCurrentUserPersonalDetails().login resolve to the same primary-login string, so once Onyx is populated both feed canMemberRead the identical value and the two pages agree.
Where they can differ, and why it's low-risk either way
The provider builds the object as {...personalDetails[accountID], accountID, email: session.email} — note email is overridden to session.email, but login is not; it comes straight from PERSONAL_DETAILS_LIST. So the only realistic divergence is a timing one: session.email is available immediately, whereas personalDetails[accountID].login is '' until that Onyx key loads.
- During that load window,
WorkspaceWorkflowsPagepasseslogin=''→canReadWorkflowsis briefly false → it renders its Not Found gate. Our page, usingsession.email, would already show the link. - That's the exact drift your question points at: for a split second our page could show a working-looking link while the destination still evaluates to Not Found. In practice the current user's personal details are essentially always loaded (and it self-corrects on the next render), so this is a very narrow, self-healing window — and the safe fallback (plain text / Not Found) is what shows if anything, never a wrong grant.
So session.email is arguably more robust at first paint, but useCurrentUserPersonalDetails().login gives exact parity with the gate the destination page enforces, which is the whole intent of this PR.
Since the goal is "mirror the destination page's access check exactly," switching both sites (LimitTypeStep.tsx:60 and DynamicExpensifyCardLimitTypePage.tsx:68) to useCurrentUserPersonalDetails().login removes even the theoretical drift for a trivial cost. Want me to push that change?
| const policy = usePolicy(policyID); | ||
| const defaultFundID = useDefaultFundID(policyID); | ||
| const [cardsList] = useOnyx(`${ONYXKEYS.COLLECTION.WORKSPACE_CARDS_LIST}${defaultFundID}_${CONST.EXPENSIFY_CARD.BANK}`, {selector: filterInactiveCardsForWorkspace}); | ||
| const [currentUserLogin] = useOnyx(ONYXKEYS.SESSION, {selector: emailSelector}); |
There was a problem hiding this comment.
@MelvinBot Same login source as WorkspaceWorkflowsPage? That page feeds canMemberRead from useCurrentUserPersonalDetails().login, whereas this uses session.email — usually identical, but would matching the source keep the gate exactly in sync?
There was a problem hiding this comment.
Good catch — in the loaded (steady) state the gate won't drift, but matching the source is the more airtight choice, and I'd lean toward it.
canMemberRead ultimately does an exact key lookup: policy.employeeList[login].role. For the current user, session.email and useCurrentUserPersonalDetails().login resolve to the same primary-login string, so once Onyx is populated both feed canMemberRead the identical value and the two pages agree.
Where they can differ, and why it's low-risk either way
The provider builds the object as {...personalDetails[accountID], accountID, email: session.email} — note email is overridden to session.email, but login is not; it comes straight from PERSONAL_DETAILS_LIST. So the only realistic divergence is a timing one: session.email is available immediately, whereas personalDetails[accountID].login is '' until that Onyx key loads.
- During that load window,
WorkspaceWorkflowsPagepasseslogin=''→canReadWorkflowsis briefly false → it renders its Not Found gate. Our page, usingsession.email, would already show the link. - That's the exact drift your question points at: for a split second our page could show a working-looking link while the destination still evaluates to Not Found. In practice the current user's personal details are essentially always loaded (and it self-corrects on the next render), so this is a very narrow, self-healing window — and the safe fallback (plain text / Not Found) is what shows if anything, never a wrong grant.
So session.email is arguably more robust at first paint, but useCurrentUserPersonalDetails().login gives exact parity with the gate the destination page enforces, which is the whole intent of this PR.
Since the goal is "mirror the destination page's access check exactly," switching both sites (LimitTypeStep.tsx:60 and DynamicExpensifyCardLimitTypePage.tsx:68) to useCurrentUserPersonalDetails().login removes even the theoretical drift for a trivial cost. Want me to push that change?
|
Same situation as my previous comment — nothing changed and there's still no code fix to push. The only failing checks are process/review gates:
All actual code checks (typecheck, ESLint, unit tests, Storybook, perf, builds, etc.) are green. To clear the remaining checks: the human co-author fills out the author checklist + Tests/QA steps, then the PR needs a review and independent approval. |
Reviewer Checklist
Screenshots/VideosAndroid: HybridAppScreen.Recording.2026-08-14.at.11.27.50.movAndroid: mWeb ChromeScreen.Recording.2026-08-14.at.11.34.58.moviOS: HybridAppScreen.Recording.2026-08-14.at.11.37.35.moviOS: mWeb SafariScreen.Recording.2026-08-14.at.11.26.54.movMacOS: Chrome / SafariScreen.Recording.2026-08-14.at.11.23.03.movScreen.Recording.2026-08-14.at.11.23.18.mov |
|
Still nothing to push — the only failing checks are process gates, not code:
All actual code checks (typecheck, ESLint, unit tests, Storybook, perf, builds) are green. To clear these two, the human co-author needs to complete the author checklist and fill out the Tests/QA steps, then get the PR reviewed and approved. There's no code fix I can make for either. |
|
The only failing checks are still the review/process gates — there's no code failure to push a fix for:
Every actual code check is green (typecheck, ESLint, unit tests, Storybook, perf, builds, etc.). These two clear once the author checklist is completed and the PR is reviewed/approved — neither is something I can resolve by committing code. |
|
Still nothing to push a code fix for — the only two failing checks are the same process/review gates as before:
Every actual code check is green (typecheck, ESLint, unit tests, Storybook, perf, builds, etc.), so there's no code change I can push to clear these. To get them green: complete the author checklist + Tests/QA steps in the PR body, then have the PR reviewed and approved. |
…t-config access Co-authored-by: truph01 <truph01@users.noreply.github.com>
|
MelvinBot has made 5 consecutive commits trying to fix CI failures. Automatic CI fix dispatches are now paused for this PR. Next steps: A human should review the recent commits and resolve the conflicting CI errors manually. |
🦜 Polyglot Parrot! 🦜Squawk! Looks like you added some shiny new English strings. Allow me to parrot them back to you in other tongues: View the translation diffdiff --git a/src/languages/de.ts b/src/languages/de.ts
index a97ffdab..aeb8c5c3 100644
--- a/src/languages/de.ts
+++ b/src/languages/de.ts
@@ -6224,7 +6224,7 @@ _Für ausführlichere Anweisungen [besuchen Sie unsere Hilfeseite](${CONST.NETSU
unassignedCards: 'Nicht zugewiesen',
integrationExport: (integration: string, type?: string) => (integration && type ? `${integration} ${type.toLowerCase()}-Export` : `${integration}-Export`),
integrationExportTitle: (integration: string, exportPageLink?: string) =>
- `Wähle das ${integration}-Konto, in das Transaktionen exportiert werden sollen. Wähle eine andere ${exportPageLink ? `<a href="${exportPageLink}">Exportoption</a>` : 'Exportoption'}, um die verfügbaren Konten zu ändern.`,
+ `Wählen Sie das ${integration}-Konto, in das die Transaktionen exportiert werden sollen. Wählen Sie eine andere ${exportPageLink ? `<a href="${exportPageLink}">Exportoption</a>` : 'Exportoption'}, um die verfügbaren Konten zu ändern.`,
lastUpdated: 'Zuletzt aktualisiert',
transactionStartDate: 'Transaktionsstartdatum',
updateCard: 'Karte aktualisieren',
@@ -6716,7 +6716,7 @@ Der Control-Tarif beginnt bei 9 $ pro aktivem Mitglied und Monat.`,
smartLimit: 'Intelligentes Limit',
smartLimitDescription: 'Bis zu einem bestimmten Betrag ausgeben, bevor eine Genehmigung erforderlich ist',
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
- `<muted-text-label>Bis zu einem bestimmten Betrag ausgeben, bevor eine Genehmigung erforderlich ist. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Aktiviere Genehmigungen</a>` : 'Aktiviere Genehmigungen'}, um diese Option auszuwählen.</muted-text-label>`,
+ `<muted-text-label>Geben Sie nur bis zu einem bestimmten Betrag aus, bevor eine Genehmigung erforderlich ist. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Freigaben aktivieren</a>` : 'Genehmigungen aktivieren'}, um diese Option auszuwählen.</muted-text-label>`,
monthly: 'Monatlich',
monthlyDescription: 'Bis zu einem bestimmten Betrag pro Monat ausgeben',
fixedAmount: 'Fester Betrag',
diff --git a/src/languages/el.ts b/src/languages/el.ts
index a179d130..ae8e4a8b 100644
--- a/src/languages/el.ts
+++ b/src/languages/el.ts
@@ -6360,7 +6360,7 @@ _Για πιο αναλυτικές οδηγίες, [επισκεφθείτε τ
unassignedCards: 'Μη εκχωρημένο',
integrationExport: (integration: string, type?: string) => (integration && type ? `εξαγωγή ${integration} ${type.toLowerCase()}` : `εξαγωγή ${integration}`),
integrationExportTitle: (integration: string, exportPageLink?: string) =>
- `Επιλέξτε τον λογαριασμό ${integration} στον οποίο θα εξαχθούν οι συναλλαγές. Επιλέξτε μια διαφορετική ${exportPageLink ? `<a href="${exportPageLink}">επιλογή εξαγωγής</a>` : 'επιλογή εξαγωγής'} για να αλλάξετε τους διαθέσιμους λογαριασμούς.`,
+ `Επιλέξτε τον λογαριασμό ${integration} στον οποίο θα εξαχθούν οι συναλλαγές. Επιλέξτε διαφορετικό ${exportPageLink ? `<a href="${exportPageLink}">επιλογή εξαγωγής</a>` : 'επιλογή εξαγωγής'} για να αλλάξετε τους διαθέσιμους λογαριασμούς.`,
lastUpdated: 'Τελευταία ενημέρωση',
transactionStartDate: 'Ημερομηνία έναρξης συναλλαγής',
updateCard: 'Ενημέρωση κάρτας',
@@ -6869,7 +6869,7 @@ _Για πιο αναλυτικές οδηγίες, [επισκεφθείτε τ
smartLimit: 'Έξυπνο όριο',
smartLimitDescription: 'Ξοδέψτε έως ένα συγκεκριμένο ποσό πριν απαιτείται έγκριση',
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
- `<muted-text-label>Ξοδέψτε έως ένα συγκεκριμένο ποσό πριν απαιτείται έγκριση. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Ενεργοποιήστε τις εγκρίσεις</a>` : 'Ενεργοποιήστε τις εγκρίσεις'} για να επιλέξετε αυτήν την επιλογή.</muted-text-label>`,
+ `<muted-text-label>Ξοδέψτε έως ένα συγκεκριμένο ποσό προτού απαιτείται έγκριση. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Ενεργοποιήστε εγκρίσεις</a>` : 'Ενεργοποίηση εγκρίσεων'} για να επιλέξετε αυτήν την επιλογή.</muted-text-label>`,
monthly: 'Μηνιαίως',
monthlyDescription: 'Το όριο ανανεώνεται μηνιαία',
fixedAmount: 'Σταθερό ποσό',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index cfb1b8b9..5730d152 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -6101,8 +6101,8 @@ ${amount} para ${merchant} - ${date}`,
assignedCards: 'Asignadas',
unassignedCards: 'No asignadas',
integrationExport: (integration, type) => (integration && type ? `Exportación a ${integration} ${type.toLowerCase()}` : `Exportación a ${integration}`),
- integrationExportTitle: (integration, exportPageLink) =>
- `Seleccione la cuenta ${integration} donde se deben exportar las transacciones. Seleccione una cuenta diferente ${exportPageLink ? `<a href="${exportPageLink}">opción de exportación</a>` : 'opción de exportación'} para cambiar las cuentas disponibles.`,
+ integrationExportTitle: (integration: string, exportPageLink?: string) =>
+ `Elige la cuenta de ${integration} a la que se deben exportar las transacciones. Selecciona un ${exportPageLink ? `<a href="${exportPageLink}">opción de exportación</a>` : 'opción de exportación'} diferente para cambiar las cuentas disponibles.`,
lastUpdated: 'Última actualización',
transactionStartDate: 'Fecha de inicio de transacciones',
updateCard: 'Actualizar tarjeta',
@@ -6887,7 +6887,7 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`,
smartLimit: 'Límite inteligente',
smartLimitDescription: 'Gasta hasta un determinado importe antes de requerir aprobación',
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
- `<muted-text-label>Gasta hasta un determinado importe antes de requerir aprobación. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Habilita las aprobaciones</a>` : 'Habilita las aprobaciones'} para seleccionar esta opción.</muted-text-label>`,
+ `<muted-text-label>Gasta hasta una cierta cantidad antes de requerir aprobación. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Habilitar aprobaciones</a>` : 'Habilitar aprobaciones'} para seleccionar esta opción.</muted-text-label>`,
monthly: 'Mensual',
monthlyDescription: 'Gasta hasta un determinado importe al mes',
fixedAmount: 'Importe fijo',
diff --git a/src/languages/fr.ts b/src/languages/fr.ts
index 62e262a6..c3a0d36a 100644
--- a/src/languages/fr.ts
+++ b/src/languages/fr.ts
@@ -6247,7 +6247,7 @@ _Pour des instructions plus détaillées, [visitez notre site d’aide](${CONST.
unassignedCards: 'Non assigné',
integrationExport: (integration: string, type?: string) => (integration && type ? `exportation ${integration} ${type.toLowerCase()}` : `Export ${integration}`),
integrationExportTitle: (integration: string, exportPageLink?: string) =>
- `Choisissez le compte ${integration} vers lequel les transactions doivent être exportées. Sélectionnez une autre ${exportPageLink ? `<a href="${exportPageLink}">option d’exportation</a>` : 'option d’exportation'} pour modifier les comptes disponibles.`,
+ `Choisissez le compte ${integration} vers lequel les transactions doivent être exportées. Sélectionnez un autre ${exportPageLink ? `<a href="${exportPageLink}">option d’export</a>` : 'option d’exportation'} pour modifier les comptes disponibles.`,
lastUpdated: 'Dernière mise à jour',
transactionStartDate: 'Date de début de la transaction',
updateCard: 'Mettre à jour la carte',
@@ -6741,7 +6741,7 @@ Le forfait Control commence à 9 $ par Membre actif et par mois.`,
smartLimit: 'Limite intelligente',
smartLimitDescription: 'Dépenser jusqu’à un certain montant avant de nécessiter une approbation',
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
- `<muted-text-label>Dépenser jusqu’à un certain montant avant de nécessiter une approbation. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Activez les approbations</a>` : 'Activez les approbations'} pour sélectionner cette option.</muted-text-label>`,
+ `<muted-text-label>Dépensez jusqu’à un certain montant avant d’exiger une approbation. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Activer les approbations</a>` : 'Activer les approbations'} pour sélectionner cette option.</muted-text-label>`,
monthly: 'Mensuel',
monthlyDescription: 'Dépenser jusqu’à un certain montant par mois',
fixedAmount: 'Montant fixe',
diff --git a/src/languages/it.ts b/src/languages/it.ts
index 9d2d31a8..376150a1 100644
--- a/src/languages/it.ts
+++ b/src/languages/it.ts
@@ -6199,7 +6199,7 @@ _Per istruzioni più dettagliate, [visita il nostro sito di assistenza](${CONST.
unassignedCards: 'Non assegnato',
integrationExport: (integration: string, type?: string) => (integration && type ? `esportazione ${integration} ${type.toLowerCase()}` : `Esportazione ${integration}`),
integrationExportTitle: (integration: string, exportPageLink?: string) =>
- `Scegli il conto ${integration} in cui esportare le transazioni. Seleziona un’altra ${exportPageLink ? `<a href="${exportPageLink}">opzione di esportazione</a>` : 'opzione di esportazione'} per modificare i conti disponibili.`,
+ `Scegli il conto ${integration} in cui esportare le transazioni. Seleziona un altro ${exportPageLink ? `<a href="${exportPageLink}">opzione di esportazione</a>` : 'opzione di esportazione'} per modificare i conti disponibili.`,
lastUpdated: 'Ultimo aggiornamento',
transactionStartDate: 'Data di inizio transazione',
updateCard: 'Aggiorna carta',
@@ -6689,7 +6689,7 @@ Il piano Control parte da 9 $ al mese per ogni membro attivo.`,
smartLimit: 'Limite intelligente',
smartLimitDescription: 'Spendi fino a un certo importo prima di richiedere l’approvazione',
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
- `<muted-text-label>Spendi fino a un certo importo prima di richiedere l’approvazione. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Abilita le approvazioni</a>` : 'Abilita le approvazioni'} per selezionare questa opzione.</muted-text-label>`,
+ `<muted-text-label>Spendi fino a un certo importo prima di richiedere l’approvazione. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Attiva approvazioni</a>` : 'Abilita approvazioni'} per selezionare questa opzione.</muted-text-label>`,
monthly: 'Mensile',
monthlyDescription: 'Spendi fino a un certo importo al mese',
fixedAmount: 'Importo fisso',
diff --git a/src/languages/ja.ts b/src/languages/ja.ts
index 2c955e3e..63d2a8aa 100644
--- a/src/languages/ja.ts
+++ b/src/languages/ja.ts
@@ -6119,7 +6119,7 @@ _詳しい手順については、[ヘルプサイトをご覧ください](${CO
unassignedCards: '未割り当て',
integrationExport: (integration: string, type?: string) => (integration && type ? `${integration} ${type.toLowerCase()} エクスポート` : `${integration} エクスポート`),
integrationExportTitle: (integration: string, exportPageLink?: string) =>
- `取引をエクスポートする${integration}アカウントを選択してください。利用可能なアカウントを変更するには、別の${exportPageLink ? `<a href="${exportPageLink}">エクスポートオプション</a>` : 'エクスポートオプション'}を選択してください。`,
+ `取引をエクスポートする ${integration} の口座を選択してください。利用可能な口座を変更するには、別の ${exportPageLink ? `<a href="${exportPageLink}">エクスポートオプション</a>` : 'エクスポートオプション'} を選択してください。`,
lastUpdated: '最終更新日時',
transactionStartDate: '取引開始日',
updateCard: 'カードを更新',
@@ -6607,7 +6607,7 @@ Control プランは、アクティブメンバー1人あたり月額 $9 から
smartLimit: 'スマート制限',
smartLimitDescription: '承認が必要になる前に、一定額まで支出できる',
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
- `<muted-text-label>承認が必要になる前に、一定額まで支出できます。このオプションを選択するには${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">承認を有効化</a>` : '承認を有効化'}してください。</muted-text-label>`,
+ `<muted-text-label>承認が必要になる前に、一定額まで利用できます。このオプションを選択するには ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">承認を有効にする</a>` : '承認を有効にする'}。</muted-text-label>`,
monthly: '毎月',
monthlyDescription: '毎月、一定額まで利用する',
fixedAmount: '固定額',
diff --git a/src/languages/nl.ts b/src/languages/nl.ts
index 09c8d7ca..66e20c11 100644
--- a/src/languages/nl.ts
+++ b/src/languages/nl.ts
@@ -6675,7 +6675,7 @@ Het Control-abonnement begint bij $9 per actieve deelnemer per maand.`,
smartLimit: 'Slimme limiet',
smartLimitDescription: 'Tot een bepaald bedrag uitgeven voordat goedkeuring vereist is',
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
- `<muted-text-label>Tot een bepaald bedrag uitgeven voordat goedkeuring vereist is. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Schakel goedkeuringen in</a>` : 'Schakel goedkeuringen in'} om deze optie te selecteren.</muted-text-label>`,
+ `<muted-text-label>Tot een bepaald bedrag uitgeven voordat goedkeuring nodig is. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Goedkeuringen inschakelen</a>` : 'Goedkeuringen inschakelen'} om deze optie te selecteren.</muted-text-label>`,
monthly: 'Maandelijks',
monthlyDescription: 'Geef per maand tot een bepaald bedrag uit',
fixedAmount: 'Vast bedrag',
diff --git a/src/languages/pl.ts b/src/languages/pl.ts
index e7a4a29d..e68e2632 100644
--- a/src/languages/pl.ts
+++ b/src/languages/pl.ts
@@ -6165,7 +6165,7 @@ _Aby uzyskać bardziej szczegółowe instrukcje, [odwiedź naszą stronę pomocy
unassignedCards: 'Nieprzypisane',
integrationExport: (integration: string, type?: string) => (integration && type ? `eksport ${integration} ${type.toLowerCase()}` : `Eksport z ${integration}`),
integrationExportTitle: (integration: string, exportPageLink?: string) =>
- `Wybierz konto ${integration}, do którego mają być eksportowane transakcje. Wybierz inną ${exportPageLink ? `<a href="${exportPageLink}">opcję eksportu</a>` : 'opcję eksportu'}, aby zmienić dostępne konta.`,
+ `Wybierz konto ${integration}, do którego mają być eksportowane transakcje. Wybierz inne ${exportPageLink ? `<a href="${exportPageLink}">opcja eksportu</a>` : 'opcja eksportu'}, aby zmienić dostępne konta.`,
lastUpdated: 'Ostatnia aktualizacja',
transactionStartDate: 'Data początkowa transakcji',
updateCard: 'Zaktualizuj kartę',
@@ -6654,7 +6654,7 @@ Plan Control zaczyna się od 9 USD za aktywnego członka miesięcznie.`,
smartLimit: 'Inteligentny limit',
smartLimitDescription: 'Wydawaj do określonej kwoty przed wymaganiem zatwierdzenia',
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
- `<muted-text-label>Wydawaj do określonej kwoty przed wymaganiem zatwierdzenia. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Włącz zatwierdzanie</a>` : 'Włącz zatwierdzanie'}, aby wybrać tę opcję.</muted-text-label>`,
+ `<muted-text-label>Wydawaj do określonej kwoty, zanim będzie wymagana akceptacja. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Włącz zatwierdzanie</a>` : 'Włącz zatwierdzanie'}, aby wybrać tę opcję.</muted-text-label>`,
monthly: 'Miesięcznie',
monthlyDescription: 'Wydawaj do określonej kwoty miesięcznie',
fixedAmount: 'Stała kwota',
diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts
index 70b620a6..4416df19 100644
--- a/src/languages/pt-BR.ts
+++ b/src/languages/pt-BR.ts
@@ -6187,7 +6187,7 @@ _Para instruções mais detalhadas, [visite nossa central de ajuda](${CONST.NETS
unassignedCards: 'Não atribuído',
integrationExport: (integration: string, type?: string) => (integration && type ? `exportação ${integration} ${type.toLowerCase()}` : `Exportação do ${integration}`),
integrationExportTitle: (integration: string, exportPageLink?: string) =>
- `Escolha a conta do ${integration} para a qual as transações devem ser exportadas. Selecione uma ${exportPageLink ? `<a href="${exportPageLink}">opção de exportação</a>` : 'opção de exportação'} diferente para alterar as contas disponíveis.`,
+ `Escolha a conta do ${integration} para onde as transações devem ser exportadas. Selecione um(a) ${exportPageLink ? `<a href="${exportPageLink}">opção de exportação</a>` : 'opção de exportação'} diferente para alterar as contas disponíveis.`,
lastUpdated: 'Última atualização',
transactionStartDate: 'Data de início da transação',
updateCard: 'Atualizar cartão',
@@ -6675,7 +6675,7 @@ O plano Control começa em US$ 9 por membro ativo por mês.`,
smartLimit: 'Limite Inteligente',
smartLimitDescription: 'Gaste até um determinado valor antes de exigir aprovação',
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
- `<muted-text-label>Gaste até um determinado valor antes de exigir aprovação. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Ative as aprovações</a>` : 'Ative as aprovações'} para selecionar esta opção.</muted-text-label>`,
+ `<muted-text-label>Gaste até um certo valor antes de exigir aprovação. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Ativar aprovações</a>` : 'Ativar aprovações'} para selecionar esta opção.</muted-text-label>`,
monthly: 'Mensal',
monthlyDescription: 'Gastar até um determinado valor por mês',
fixedAmount: 'Valor fixo',
diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts
index 3884dd7d..8866250c 100644
--- a/src/languages/zh-hans.ts
+++ b/src/languages/zh-hans.ts
@@ -5975,7 +5975,7 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM
unassignedCards: '未分配',
integrationExport: (integration: string, type?: string) => (integration && type ? `${integration} ${type.toLowerCase()} 导出` : `${integration} 导出`),
integrationExportTitle: (integration: string, exportPageLink?: string) =>
- `选择要导出交易的 ${integration} 账户。选择其他${exportPageLink ? `<a href="${exportPageLink}">导出选项</a>` : '导出选项'}以更改可用账户。`,
+ `选择要导出交易记录的 ${integration} 账户。选择其他 ${exportPageLink ? `<a href="${exportPageLink}">导出选项</a>` : '导出选项'} 以更改可用账户。`,
lastUpdated: '最近更新',
transactionStartDate: '交易开始日期',
updateCard: '更新卡片',
@@ -6447,7 +6447,7 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM
smartLimit: '智能限额',
smartLimitDescription: '在需要审批前可支出至指定金额上限',
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
- `<muted-text-label>在需要审批前可支出至指定金额上限。${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">启用审批</a>` : '启用审批'}后即可选择此选项。</muted-text-label>`,
+ `<muted-text-label>在需要审批前可花费至多一定金额。${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">启用审批</a>` : '启用审批'}以选择此选项。</muted-text-label>`,
monthly: '每月',
monthlyDescription: '每月消费上限为特定金额',
fixedAmount: '固定金额',
Note You can apply these changes to your branch by copying the patch to your clipboard, then running |
|
@MelvinBot Revert the changes you made to |
…ut export-config access" This reverts commit 2b2776e.
|
Done — reverted the out-of-scope That commit ( |
joekaufmanexpensify
left a comment
There was a problem hiding this comment.
Good for product.
Explanation of Change
The "Enable approvals" inline description link on the Expensify Card limit-type surfaces was rendered unconditionally, even for viewers who lack access to the linked Workflows destination. A
CARD_ADMIN(granular role) can reach these card pages but not the Workflows page, so clicking the link dropped them onto the "Not here" (Not Found) page — the deploy-blocker behavior reported in the issue.This PR gates the "Enable approvals" link on the same access check the destination page enforces, and when the viewer lacks access the description now renders as plain (non-linked) text instead of a dead-end hyperlink (the option requested by
truph01). Admins keep the link exactly as before.Sites fixed:
LimitTypeStep.tsx(issue-new-card flow) andDynamicExpensifyCardLimitTypePage.tsx(edit-existing-card flow). Gated oncanMemberRead(policy, currentUserLogin, POLICY_FEATURE.WORKFLOWS), matching theWorkspaceWorkflowsPagecanReadWorkflowsgate.The
smartLimitDisabledDescriptioncopy string now accepts an optional link argument and renders the anchor text as plain text when no link is passed. This is mirrored across all locale files.Fixed Issues
$ #98490
PROPOSAL: #98490 (comment)
Tests
// TODO: The human co-author must fill out the manual tests before marking this PR as "ready for review".
Suggested repro (needs a Control workspace with the Expensify Card enabled, Optional approvals, and a separate user invited as a card admin):
Offline tests
QA Steps
// TODO: These must be filled out, or the issue title must include "[No QA]."
PR Author Checklist
### Fixed Issuessection aboveTestssectionOffline stepssectionQA stepssectionAvatar, I verified the components usingAvatarare working as expected)StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))npm run compress-svg)Avataris modified, I verified thatAvataris working as expected in all cases)Designlabel and/or tagged@Expensify/designso the design team can review the changes.mainbranch was merged into this PR after a review, I tested again and verified the outcome was still expected according to theTeststeps.Screenshots/Videos
Android: Native
Android: mWeb Chrome
iOS: Native
iOS: mWeb Safari
MacOS: Chrome / Safari