Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6715,8 +6715,8 @@ Der Control-Tarif beginnt bei 9 $ pro aktivem Mitglied und Monat.`,
chooseLimitType: 'Wähle einen Limittyp',
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. <a href="${workspaceWorkflowsLink}">Aktiviere Genehmigungen</a>, um diese Option auszuwählen.</muted-text-label>`,
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>`,
monthly: 'Monatlich',
monthlyDescription: 'Bis zu einem bestimmten Betrag pro Monat ausgeben',
fixedAmount: 'Fester Betrag',
Expand Down
4 changes: 2 additions & 2 deletions src/languages/el.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6868,8 +6868,8 @@ _Για πιο αναλυτικές οδηγίες, [επισκεφθείτε τ
chooseLimitType: 'Επιλέξτε τύπο ορίου',
smartLimit: 'Έξυπνο όριο',
smartLimitDescription: 'Ξοδέψτε έως ένα συγκεκριμένο ποσό πριν απαιτείται έγκριση',
smartLimitDisabledDescription: (workspaceWorkflowsLink: string) =>
`<muted-text-label>Ξοδέψτε έως ένα συγκεκριμένο ποσό πριν απαιτείται έγκριση. <a href="${workspaceWorkflowsLink}">Ενεργοποιήστε τις εγκρίσεις</a> για να επιλέξετε αυτήν την επιλογή.</muted-text-label>`,
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
`<muted-text-label>Ξοδέψτε έως ένα συγκεκριμένο ποσό πριν απαιτείται έγκριση. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Ενεργοποιήστε τις εγκρίσεις</a>` : 'Ενεργοποιήστε τις εγκρίσεις'} για να επιλέξετε αυτήν την επιλογή.</muted-text-label>`,
monthly: 'Μηνιαίως',
monthlyDescription: 'Το όριο ανανεώνεται μηνιαία',
fixedAmount: 'Σταθερό ποσό',
Expand Down
4 changes: 2 additions & 2 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6819,8 +6819,8 @@ const translations = {
chooseLimitType: 'Choose a limit type',
smartLimit: 'Smart Limit',
smartLimitDescription: 'Spend up to a certain amount before requiring approval',
smartLimitDisabledDescription: (workspaceWorkflowsLink: string) =>
`<muted-text-label>Spend up to a certain amount before requiring approval. <a href="${workspaceWorkflowsLink}">Enable approvals</a> to select this option.</muted-text-label>`,
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
`<muted-text-label>Spend up to a certain amount before requiring approval. ${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">Enable approvals</a>` : 'Enable approvals'} to select this option.</muted-text-label>`,
monthly: 'Monthly',
monthlyDescription: 'Limit renews monthly',
fixedAmount: 'Fixed amount',
Expand Down
4 changes: 2 additions & 2 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6886,8 +6886,8 @@ El plan Controlar empieza en 9 $ por miembro activo al mes.`,
chooseLimitType: 'Elegir un tipo de límite',
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. <a href="${workspaceWorkflowsLink}">Habilita las aprobaciones</a> para seleccionar esta opción.</muted-text-label>`,
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>`,
monthly: 'Mensual',
monthlyDescription: 'Gasta hasta un determinado importe al mes',
fixedAmount: 'Importe fijo',
Expand Down
4 changes: 2 additions & 2 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6740,8 +6740,8 @@ Le forfait Control commence à 9 $ par Membre actif et par mois.`,
chooseLimitType: 'Choisissez un type de limite',
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. <a href="${workspaceWorkflowsLink}">Activez les approbations</a> pour sélectionner cette option.</muted-text-label>`,
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>`,
monthly: 'Mensuel',
monthlyDescription: 'Dépenser jusqu’à un certain montant par mois',
fixedAmount: 'Montant fixe',
Expand Down
4 changes: 2 additions & 2 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6688,8 +6688,8 @@ Il piano Control parte da 9 $ al mese per ogni membro attivo.`,
chooseLimitType: 'Scegli un tipo di limite',
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. <a href="${workspaceWorkflowsLink}">Abilita le approvazioni</a> per selezionare questa opzione.</muted-text-label>`,
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>`,
monthly: 'Mensile',
monthlyDescription: 'Spendi fino a un certo importo al mese',
fixedAmount: 'Importo fisso',
Expand Down
4 changes: 2 additions & 2 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6606,8 +6606,8 @@ Control プランは、アクティブメンバー1人あたり月額 $9 から
chooseLimitType: '上限の種類を選択',
smartLimit: 'スマート制限',
smartLimitDescription: '承認が必要になる前に、一定額まで支出できる',
smartLimitDisabledDescription: (workspaceWorkflowsLink: string) =>
`<muted-text-label>承認が必要になる前に、一定額まで支出できます。このオプションを選択するには<a href="${workspaceWorkflowsLink}">承認を有効化</a>してください。</muted-text-label>`,
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
`<muted-text-label>承認が必要になる前に、一定額まで支出できます。このオプションを選択するには${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">承認を有効化</a>` : '承認を有効化'}してください。</muted-text-label>`,
monthly: '毎月',
monthlyDescription: '毎月、一定額まで利用する',
fixedAmount: '固定額',
Expand Down
4 changes: 2 additions & 2 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6674,8 +6674,8 @@ Het Control-abonnement begint bij $9 per actieve deelnemer per maand.`,
chooseLimitType: 'Kies een limiettype',
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. <a href="${workspaceWorkflowsLink}">Schakel goedkeuringen in</a> om deze optie te selecteren.</muted-text-label>`,
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>`,
monthly: 'Maandelijks',
monthlyDescription: 'Geef per maand tot een bepaald bedrag uit',
fixedAmount: 'Vast bedrag',
Expand Down
4 changes: 2 additions & 2 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6653,8 +6653,8 @@ Plan Control zaczyna się od 9 USD za aktywnego członka miesięcznie.`,
chooseLimitType: 'Wybierz typ limitu',
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. <a href="${workspaceWorkflowsLink}">Włącz zatwierdzanie</a>, aby wybrać tę opcję.</muted-text-label>`,
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>`,
monthly: 'Miesięcznie',
monthlyDescription: 'Wydawaj do określonej kwoty miesięcznie',
fixedAmount: 'Stała kwota',
Expand Down
4 changes: 2 additions & 2 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6674,8 +6674,8 @@ O plano Control começa em US$ 9 por membro ativo por mês.`,
chooseLimitType: 'Escolha um tipo de limite',
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. <a href="${workspaceWorkflowsLink}">Ative as aprovações</a> para selecionar esta opção.</muted-text-label>`,
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>`,
monthly: 'Mensal',
monthlyDescription: 'Gastar até um determinado valor por mês',
fixedAmount: 'Valor fixo',
Expand Down
4 changes: 2 additions & 2 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6446,8 +6446,8 @@ _如需更详细的说明,请[访问我们的帮助网站](${CONST.NETSUITE_IM
chooseLimitType: '选择限额类型',
smartLimit: '智能限额',
smartLimitDescription: '在需要审批前可支出至指定金额上限',
smartLimitDisabledDescription: (workspaceWorkflowsLink: string) =>
`<muted-text-label>在需要审批前可支出至指定金额上限。<a href="${workspaceWorkflowsLink}">启用审批</a>后即可选择此选项。</muted-text-label>`,
smartLimitDisabledDescription: (workspaceWorkflowsLink?: string) =>
`<muted-text-label>在需要审批前可支出至指定金额上限。${workspaceWorkflowsLink ? `<a href="${workspaceWorkflowsLink}">启用审批</a>` : '启用审批'}后即可选择此选项。</muted-text-label>`,
monthly: '每月',
monthlyDescription: '每月消费上限为特定金额',
fixedAmount: '固定金额',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {openPolicyEditCardLimitTypePage} from '@libs/actions/Policy/Policy';
import {filterInactiveCardsForWorkspace, getDefaultExpensifyCardLimitType} from '@libs/CardUtils';
import DateUtils from '@libs/DateUtils';
import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types';
import {getApprovalWorkflow} from '@libs/PolicyUtils';
import {canMemberRead, getApprovalWorkflow} from '@libs/PolicyUtils';

import Navigation from '@navigation/Navigation';
import type {SettingsNavigatorParamList} from '@navigation/types';
Expand All @@ -46,6 +46,7 @@ import INPUT_IDS from '@src/types/form/EditExpensifyCardLimitTypeForm';
import type {CardLimitType} from '@src/types/onyx/Card';

import {useFocusEffect} from '@react-navigation/native';
import {emailSelector} from '@selectors/Session';
import {format, toZonedTime} from 'date-fns-tz';
import React, {useEffect, useRef, useState} from 'react';
import {View} from 'react-native';
Expand All @@ -64,6 +65,7 @@ function DynamicExpensifyCardLimitTypePage({route}: WorkspaceEditCardLimitTypePa
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});

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.

@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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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, WorkspaceWorkflowsPage passes login=''canReadWorkflows is briefly false → it renders its Not Found gate. Our page, using session.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?

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.

@MelvinBot Yes, apply it


const card = cardsList?.[cardID];
// Keep the latest card snapshot so a confirmation that resolves after the card refreshes (e.g. while the
Expand Down Expand Up @@ -170,7 +172,10 @@ function DynamicExpensifyCardLimitTypePage({route}: WorkspaceEditCardLimitTypePa
}
}

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
// access would otherwise be dropped onto the Not Found page. When they lack access, render plain (non-linked) text.
const canReadWorkflows = canMemberRead(policy, currentUserLogin ?? '', CONST.POLICY.POLICY_FEATURE.WORKFLOWS);
const workspaceWorkflowsPageURL = canReadWorkflows ? `${environmentURL}/${ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID)}` : undefined;

const data = [];

Expand Down
10 changes: 8 additions & 2 deletions src/pages/workspace/expensifyCard/issueNew/LimitTypeStep.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import {setIssueNewCardStepAndData} from '@libs/actions/Card';
import {getDefaultExpensifyCardLimitType} from '@libs/CardUtils';
import {convertToBackendAmount, convertToFrontendAmountAsString} from '@libs/CurrencyUtils';
import {getApprovalWorkflow, isPolicyFeatureEnabled} from '@libs/PolicyUtils';
import {canMemberRead, getApprovalWorkflow, isPolicyFeatureEnabled} from '@libs/PolicyUtils';
import {getFieldRequiredErrors} from '@libs/ValidationUtils';

import CONST from '@src/CONST';
Expand All @@ -31,6 +31,7 @@ import KeyboardUtils from '@src/utils/keyboard';

import type {OnyxEntry} from 'react-native-onyx';

import {emailSelector} from '@selectors/Session';
import React, {useCallback, useMemo, useRef, useState} from 'react';
import {View} from 'react-native';

Expand All @@ -56,6 +57,11 @@ function LimitTypeStep({policy, stepNames, startStepIndex}: LimitTypeStepProps)
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});

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.

@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?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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, WorkspaceWorkflowsPage passes login=''canReadWorkflows is briefly false → it renders its Not Found gate. Our page, using session.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?


// 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.
const canReadWorkflows = canMemberRead(policy, currentUserLogin ?? '', CONST.POLICY.POLICY_FEATURE.WORKFLOWS);

const areApprovalsConfigured = getApprovalWorkflow(policy) !== CONST.POLICY.APPROVAL_MODE.OPTIONAL;
const defaultType = getDefaultExpensifyCardLimitType(policy);
Expand Down Expand Up @@ -102,7 +108,7 @@ function LimitTypeStep({policy, stepNames, startStepIndex}: LimitTypeStepProps)
setIssueNewCardStepAndData({step: CONST.EXPENSIFY_CARD.STEP.CARD_TYPE, policyID});
}, [isEditing, policyID]);

const workspaceWorkflowsPageURL = `${environmentURL}/${ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID)}`;
const workspaceWorkflowsPageURL = canReadWorkflows ? `${environmentURL}/${ROUTES.WORKSPACE_WORKFLOWS.getRoute(policyID)}` : undefined;

const data = useMemo(() => {
const options = [];
Expand Down
Loading