Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
efcc12e
Add approve-only workspace role
ShridharGoel Oct 3, 2026
3ae8818
Gate expense creation on canRoleCreateExpenses helper
ShridharGoel Oct 3, 2026
737b1f3
Gate expense creation on CREATE_EXPENSES permission instead of role c…
ShridharGoel Oct 3, 2026
3a47f56
Update approve-only role picker subtext copy
ShridharGoel Oct 3, 2026
7c67c2b
Hide create report option in workspace chat for approve-only members
ShridharGoel Oct 3, 2026
1caa665
Show empty state on workspace rooms page when no rooms exist
ShridharGoel Oct 3, 2026
304de0c
Skip optimistic workspace chat for approve-only invites
ShridharGoel Oct 3, 2026
a04e193
Test approve-only workspace exclusion from recipient picker
ShridharGoel Oct 3, 2026
9d74ab4
Show the new role in the demoted-from-workspace message
ShridharGoel Oct 4, 2026
2e1a84f
Add a save button to the member role page and space out the blocked m…
ShridharGoel Oct 4, 2026
1de8c2b
Fix type errors in role gates and tests
ShridharGoel Oct 5, 2026
e1fba7c
Merge remote-tracking branch 'upstream/main' into approve-only-role
ShridharGoel Oct 5, 2026
9157b95
Use currentRole for the approve-only entry after upstream refactor
ShridharGoel Oct 5, 2026
690ed5c
Update OptionsListUtilsTest.tsx
ShridharGoel Oct 5, 2026
6198974
Merge remote-tracking branch 'upstream/main' into approve-only-role
ShridharGoel Oct 7, 2026
ba5b275
Merge remote-tracking branch 'fork/approve-only-role' into approve-on…
ShridharGoel Oct 7, 2026
5384b65
Make gate comments permission-oriented
ShridharGoel Oct 7, 2026
dcf8257
Handle role update failures and name blocked-reason constants
ShridharGoel Oct 7, 2026
2dad908
Merge remote-tracking branch 'upstream/main' into approve-only-role
ShridharGoel Oct 8, 2026
364448c
Merge remote-tracking branch 'upstream/main' into approve-only-role
ShridharGoel Oct 8, 2026
95ec4ce
Allow promise-returning onSave in usePopoverEditState
ShridharGoel Oct 8, 2026
0026331
Merge remote-tracking branch 'upstream/main' into approve-only-role
ShridharGoel Oct 8, 2026
1e19e99
Merge remote-tracking branch 'refs/remotes/origin/main' into approve-…
ShridharGoel Oct 8, 2026
1077fc3
Add approve-only to bulk role actions and block add-expense without c…
ShridharGoel Oct 9, 2026
9175324
Gate add-expense entry points on create-expenses permission
ShridharGoel Oct 9, 2026
be58a72
Fix create-expenses gates resolving random test roles
ShridharGoel Oct 9, 2026
6fb055e
Merge branch 'main' of github.com:Expensify/App into approve-only-role
ShridharGoel Oct 9, 2026
91f6a9f
Update usePopoverEditState.ts
ShridharGoel Oct 9, 2026
5286f29
Shorten approve-only request comments
ShridharGoel Oct 9, 2026
2687707
Merge remote-tracking branch 'fork/approve-only-role' into approve-on…
ShridharGoel Oct 9, 2026
3a17394
Restore minimal disable comment for type assertion
ShridharGoel Oct 9, 2026
c8f03e1
Restore files that were reverted against latest main
ShridharGoel Oct 9, 2026
a96fa06
Rebuild branch on latest main with approve-only changes
ShridharGoel Oct 9, 2026
6dae60d
Grant guests create-expenses permission and revert optimistic role ch…
ShridharGoel Oct 9, 2026
0cfdcff
Revert approve-only role changes through failureData on the side effe…
ShridharGoel Oct 9, 2026
dd150e6
Merge branch 'main' into approve-only-role
ShridharGoel Oct 9, 2026
51a4902
Add approve-only option to the members list role filter
ShridharGoel Oct 12, 2026
981a17b
Merge remote-tracking branch 'origin/main' into approve-only-role
ShridharGoel Oct 12, 2026
bb1bd7e
Revert optimistic approve-only role on network failure for side-effec…
ShridharGoel Oct 12, 2026
af88e04
Block approve-only role change while offline
ShridharGoel Oct 12, 2026
1bd1614
Merge remote-tracking branch 'origin/main' into approve-only-role
ShridharGoel Oct 12, 2026
cdf555c
Fall back to generic error when no blocked reason has client copy
ShridharGoel Oct 13, 2026
e3ed783
Address review feedback on comments and filter naming
ShridharGoel Oct 13, 2026
f0a50df
Unify create-expenses permission helper to fail closed on unresolvabl…
ShridharGoel Oct 13, 2026
04e0ad9
Merge remote-tracking branch 'origin/main' into approve-only-role
ShridharGoel Oct 13, 2026
4a3ee28
Restore guest role UI dropped in the merge
ShridharGoel Oct 13, 2026
a4018cd
Fix missing import, compiler deps, and test cast
ShridharGoel Oct 13, 2026
9ae3130
Drop unnecessary useMemo for create report option
ShridharGoel Oct 13, 2026
fef76af
Show no inline member error for approve-only role change failures
ShridharGoel Oct 13, 2026
a77852e
Report role change failures per request transport
ShridharGoel Oct 13, 2026
32dcdfe
Merge branch 'main' into approve-only-role
ShridharGoel Oct 13, 2026
f201b01
Fix lint errors, test mock role, and errors type
ShridharGoel Oct 13, 2026
fac67fc
Merge remote-tracking branch 'fork/approve-only-role' into approve-on…
ShridharGoel Oct 13, 2026
b97f3c5
Deliver approve-only blocked reasons through Onyx and queue role upda…
ShridharGoel Oct 15, 2026
98c7d4a
Remove outdated blocked reasons comment
ShridharGoel Oct 15, 2026
36307ba
Merge branch 'main' into approve-only-role
ShridharGoel Oct 15, 2026
6e60cd3
Gate report preview and move-to-report on create-expenses permission …
ShridharGoel Oct 15, 2026
63c953f
Merge remote-tracking branch 'origin/main' into approve-only-role
ShridharGoel Oct 15, 2026
85b9bfe
Fix blocked role change Onyx export, role permission checks, and card…
ShridharGoel Oct 15, 2026
51ac72c
Merge branch 'main' into approve-only-role
ShridharGoel Oct 19, 2026
2a19db7
Update PolicyUtilsTest.ts
ShridharGoel Oct 19, 2026
aec986d
Update index.ts
ShridharGoel Oct 19, 2026
3614985
Exclude workspaces without create-expense permission from report crea…
ShridharGoel Oct 19, 2026
916c2e8
Merge remote-tracking branch 'origin/main' into approve-only-role
ShridharGoel Oct 21, 2026
4d49c88
Add approve-only to selectable roles and fix CI failures
ShridharGoel Oct 22, 2026
8a51786
Add missing formatPhoneNumber to test helper config
ShridharGoel Oct 22, 2026
af63850
Skip create-expenses gate for non-workspace reports
ShridharGoel Oct 22, 2026
1a6c99d
Updates
ShridharGoel Oct 22, 2026
8aaa59c
Update src/libs/actions/Policy/Member.ts
ShridharGoel Oct 22, 2026
1f84029
Update src/libs/PolicyUtils/permissions.ts
ShridharGoel Oct 22, 2026
8ee6dcf
Update permissions.ts
ShridharGoel Oct 22, 2026
2deaad1
Apply batched suggestions from code review
ShridharGoel Oct 22, 2026
166d64f
Remove created field from blocked role change
ShridharGoel Oct 22, 2026
edaeda5
Centralize create-expenses gate in canAddTransaction
ShridharGoel Oct 22, 2026
b68bbd5
Move POLICY_ROLE_RANKING export to export block
ShridharGoel Oct 22, 2026
80cf8e8
Merge branch 'main' into approve-only-role
ShridharGoel Oct 23, 2026
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
7 changes: 7 additions & 0 deletions src/CONST/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4949,6 +4949,12 @@ const CONST = {
CARD_ADMIN: 'cardAdmin',
PEOPLE_ADMIN: 'peopleAdmin',
PAYMENTS_ADMIN: 'paymentsAdmin',
APPROVE_ONLY: 'approveOnly',
},
APPROVE_ONLY_BLOCKED_REASONS: {
HAS_CARD_ON_POLICY: 'hasCardOnPolicy',
IS_RESTRICTED_BY_DOMAIN_GROUP: 'isRestrictedByDomainGroup',
IS_DEFAULT_POLICY: 'isDefaultPolicy',
},
WORKSPACE_STATUS: {
ACTIVE: 'active',
Expand Down Expand Up @@ -4981,6 +4987,7 @@ const CONST = {
REPORT_FIELDS: 'reportFields',
ACCOUNTING: 'accounting',
MORE_FEATURES: 'moreFeatures',
CREATE_EXPENSES: 'createExpenses',
},
POLICY_FEATURE_ACCESS: {
READ: 'read',
Expand Down
4 changes: 4 additions & 0 deletions src/ONYXKEYS.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,9 @@ const ONYXKEYS = {
/** Object containing contact method that's going to be added */
PENDING_CONTACT_ACTION: 'pendingContactAction',

/** Reasons the backend refused an approve-only role change, shown to the requesting client in a modal */
BLOCKED_ROLE_CHANGE: 'blockedRoleChange',

/** Store the information of validateCode */
VALIDATE_ACTION_CODE: 'validateActionCode',

Expand Down Expand Up @@ -1944,6 +1947,7 @@ type OnyxValuesMapping = {
[ONYXKEYS.RECENTLY_USED_REPORT_FIELDS]: OnyxTypes.RecentlyUsedReportFields;
[ONYXKEYS.RAM_ONLY_UPDATE_REQUIRED]: boolean;
[ONYXKEYS.SUPPORTAL_PERMISSION_DENIED]: OnyxTypes.SupportalPermissionDenied | null;
[ONYXKEYS.BLOCKED_ROLE_CHANGE]: OnyxTypes.BlockedRoleChange | null;
[ONYXKEYS.RESET_REQUIRED]: boolean;
[ONYXKEYS.PLAID_CURRENT_EVENT]: string;
[ONYXKEYS.NVP_PRIVATE_TAX_EXEMPT]: boolean;
Expand Down
2 changes: 2 additions & 0 deletions src/components/DeferredGlobalModals.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import useApproveOnlyRoleBlockedModal from '@hooks/useApproveOnlyRoleBlockedModal';
import useScreenShareRequestPrompt from '@hooks/useScreenShareRequestPrompt';

import React from 'react';
Expand All @@ -14,6 +15,7 @@ const LazyTrialPaymentReminderModalManager = React.lazy(() => import('./TrialPay
*/
function DeferredGlobalModals() {
useScreenShareRequestPrompt();
useApproveOnlyRoleBlockedModal();

return (
<>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import useThemeStyles from '@hooks/useThemeStyles';
import {clearCopyPolicySettings} from '@libs/actions/Policy/CopyPolicySettings';
import {callFunctionIfActionIsAllowed} from '@libs/actions/Session';
import Navigation from '@libs/Navigation/Navigation';
import {canUnarchivePolicy} from '@libs/PolicyUtils';
import {canRoleCreateExpenses, canUnarchivePolicy} from '@libs/PolicyUtils';
import shouldRenderTransferOwnerButton from '@libs/shouldRenderTransferOwnerButton';

import UnarchiveWorkspaceFlow from '@pages/workspace/archiveWorkspace/UnarchiveWorkspaceFlow';
Expand Down Expand Up @@ -135,7 +135,8 @@ function WorkspaceRowThreeDotsMenu({item, onDeleteWorkspace, onArchiveWorkspace,
}
}

if (!isDefault && !item?.isJoinRequestPending && !isRestrictedToPreferredPolicy) {
// Making the workspace the default would break expense creation routing for members without create-expenses permission.
if (!isDefault && !item?.isJoinRequestPending && !isRestrictedToPreferredPolicy && canRoleCreateExpenses(item.role)) {
menuItems.push({
icon: icons.Star,
text: translate('workspace.common.setAsDefault'),
Expand Down
11 changes: 11 additions & 0 deletions src/components/Tables/WorkspaceMembersTable/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ type WorkspaceMembersTableProps = {

const WORKSPACE_MEMBER_FILTER_VALUES = {
ADMINS: 'admins',
APPROVE_ONLY: 'approveOnly',
APPROVERS: 'approvers',
AUDITORS: 'auditors',
CARD_ADMINS: 'cardAdmins',
Expand Down Expand Up @@ -266,6 +267,11 @@ export default function WorkspaceMembersTable({
return true;
}

const isApproveOnly = item.role === CONST.POLICY.ROLE.APPROVE_ONLY;
if (filterValues.includes(WORKSPACE_MEMBER_FILTER_VALUES.APPROVE_ONLY) && isApproveOnly) {
return true;
}

const isEditor = item.role === CONST.POLICY.ROLE.EDITOR;
if (filterValues.includes(WORKSPACE_MEMBER_FILTER_VALUES.EDITORS) && isEditor) {
return true;
Expand Down Expand Up @@ -321,6 +327,11 @@ export default function WorkspaceMembersTable({
label: translate('workspace.people.guests'),
value: WORKSPACE_MEMBER_FILTER_VALUES.GUESTS,
});

filterConfig.role.options.push({
label: translate('workspace.people.approveOnly'),
value: WORKSPACE_MEMBER_FILTER_VALUES.APPROVE_ONLY,
});
}

if (isSubmitPolicy(policy)) {
Expand Down
9 changes: 8 additions & 1 deletion src/components/WorkspaceMemberRoleList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ function getAssignableWorkspaceMemberRoleItems(
isSelected: currentRole === CONST.POLICY.ROLE.USER,
keyForList: CONST.POLICY.ROLE.USER,
},
{
value: CONST.POLICY.ROLE.APPROVE_ONLY,
text: translate('workspace.common.roleName', CONST.POLICY.ROLE.APPROVE_ONLY),
alternateText: translate('workspace.common.approveOnlyAlternateText'),
isSelected: currentRole === CONST.POLICY.ROLE.APPROVE_ONLY,
keyForList: CONST.POLICY.ROLE.APPROVE_ONLY,
},
];

const selectableRoles = getSelectableRoles(policy, currentUserLogin, allowedRoles);
Expand Down Expand Up @@ -148,7 +155,7 @@ function WorkspaceMemberRoleList({
ListItem={SingleSelectListItem}
onSelectRow={onSelectRole}
confirmButtonOptions={confirmButtonOptions}
shouldSingleExecuteRowSelect
shouldSingleExecuteRowSelect={!confirmButtonOptions?.showButton}
initiallyFocusedItemKey={availableRoleItems.find((item) => item.isSelected)?.keyForList}
addBottomSafeAreaPadding
>
Expand Down
77 changes: 77 additions & 0 deletions src/hooks/useApproveOnlyRoleBlockedModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* Subscribes to BLOCKED_ROLE_CHANGE in Onyx and shows the blocked-reasons modal on the global modal stack
* when the backend refuses an approve-only role change.
*/
import {clearBlockedRoleChange, clearUpdateMemberRoleError} from '@userActions/Policy/Member';

import CONST from '@src/CONST';
import type {TranslationPaths} from '@src/languages/types';
import ONYXKEYS from '@src/ONYXKEYS';
import type BlockedRoleChange from '@src/types/onyx/BlockedRoleChange';

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

import {useEffect, useRef} from 'react';

import useConfirmModal from './useConfirmModal';
import useLocalize from './useLocalize';
import useOnyx from './useOnyx';

const APPROVE_ONLY_ROLE_BLOCKED_MODAL_ID = 'approveOnlyRoleBlocked';

const REASON_TRANSLATION_KEYS: Record<string, TranslationPaths> = {
[CONST.POLICY.APPROVE_ONLY_BLOCKED_REASONS.HAS_CARD_ON_POLICY]: 'workspace.people.approveOnlyRoleBlockedReasons.hasCardOnPolicy',
[CONST.POLICY.APPROVE_ONLY_BLOCKED_REASONS.IS_RESTRICTED_BY_DOMAIN_GROUP]: 'workspace.people.approveOnlyRoleBlockedReasons.isRestrictedByDomainGroup',
[CONST.POLICY.APPROVE_ONLY_BLOCKED_REASONS.IS_DEFAULT_POLICY]: 'workspace.people.approveOnlyRoleBlockedReasons.isDefaultPolicy',
};

function useApproveOnlyRoleBlockedModal() {
const {translate} = useLocalize();
const {showConfirmModal, closeModalByID} = useConfirmModal();
const [blockedRoleChange] = useOnyx(ONYXKEYS.BLOCKED_ROLE_CHANGE);
const lastShownRef = useRef<OnyxEntry<BlockedRoleChange>>(undefined);
const isModalOpenRef = useRef(false);

useEffect(() => {
if (!blockedRoleChange) {
if (isModalOpenRef.current) {
isModalOpenRef.current = false;
closeModalByID(APPROVE_ONLY_ROLE_BLOCKED_MODAL_ID);
}
return;
}

if (lastShownRef.current === blockedRoleChange) {
return;
}
lastShownRef.current = blockedRoleChange;
isModalOpenRef.current = true;

const {policyID, email, blockedReasons = []} = blockedRoleChange;

// `OnyxUpdates.apply` runs `request.failureData` on the microtask after `response.onyxData`, which sets a
// generic inline error on the member row. Clear that row error once `failureData` has settled so the modal
// is the only feedback shown for a blocked role change.
if (policyID && email) {
Promise.resolve().then(() => {
clearUpdateMemberRoleError(policyID, email);
});
}

const reasonLines = blockedReasons.filter((reason) => reason in REASON_TRANSLATION_KEYS).map((reason) => `• ${translate(REASON_TRANSLATION_KEYS[reason])}`);
const hasReasonLines = reasonLines.length > 0;

showConfirmModal({
id: APPROVE_ONLY_ROLE_BLOCKED_MODAL_ID,
title: translate('workspace.people.approveOnlyRoleBlockedTitle'),
prompt: hasReasonLines ? [translate('workspace.people.approveOnlyRoleBlockedDescription'), '', ...reasonLines].join('\n') : translate('common.genericErrorMessage'),
confirmText: translate(hasReasonLines ? 'workspace.people.approveOnlyRoleBlockedConfirm' : 'common.buttonConfirm'),
shouldShowCancelButton: false,
}).then(() => {
isModalOpenRef.current = false;
clearBlockedRoleChange();
});
}, [blockedRoleChange, closeModalByID, showConfirmModal, translate]);
}

export default useApproveOnlyRoleBlockedModal;
14 changes: 9 additions & 5 deletions src/hooks/useDefaultExpensePolicy.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
import isTeachersUnitePolicyID from '@libs/isTeachersUnitePolicyID';
import {isGroupPolicy, isPolicyAccessible} from '@libs/PolicyUtils';
import {canCreateExpensesOnPolicy, isGroupPolicy, isPolicyAccessible} from '@libs/PolicyUtils';

import ONYXKEYS from '@src/ONYXKEYS';
import type {Policy} from '@src/types/onyx';

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

import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails';
import useOnyx from './useOnyx';
import usePreferredPolicy from './usePreferredPolicy';

function isEligibleDefaultExpensePolicy(policy: OnyxEntry<Policy>, login: string): policy is Policy {
return !!policy && isGroupPolicy(policy) && isPolicyAccessible(policy, login) && canCreateExpensesOnPolicy(policy, login) && !isTeachersUnitePolicyID(policy.id);
}

/**
* Selector that finds the single qualifying group policy ID from the collection.
* Returns only an ID (stable) — prevents re-renders when unrelated policies change.
Expand All @@ -21,7 +25,7 @@ function getSingleGroupPolicyID(policies: OnyxCollection<Policy>, login: string)

let singlePolicyID: string | undefined;
for (const policy of Object.values(policies)) {
if (!policy || !isGroupPolicy(policy) || !isPolicyAccessible(policy, login) || isTeachersUnitePolicyID(policy.id)) {
if (!isEligibleDefaultExpensePolicy(policy, login)) {
continue;
}
if (!singlePolicyID) {
Expand Down Expand Up @@ -50,11 +54,11 @@ export default function useDefaultExpensePolicy() {
const [singleGroupPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${singleGroupPolicyID}`);

// Teachers Unite only supports expenses via split expense , so it can never be the default destination for a new expense.
if (isRestrictedToPreferredPolicy && isGroupPolicy(preferredPolicy) && isPolicyAccessible(preferredPolicy, login) && !isTeachersUnitePolicyID(preferredPolicy?.id)) {
if (isRestrictedToPreferredPolicy && isEligibleDefaultExpensePolicy(preferredPolicy, login)) {
return preferredPolicy;
}

if (isGroupPolicy(activePolicy) && isPolicyAccessible(activePolicy, login) && !isTeachersUnitePolicyID(activePolicy?.id)) {
if (isEligibleDefaultExpensePolicy(activePolicy, login)) {
return activePolicy;
}

Expand Down
9 changes: 2 additions & 7 deletions src/hooks/usePolicyForMovingExpenses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {useActivePolicyContext} from '@components/ActivePolicyProvider';
import {useSession} from '@components/OnyxListItemProvider';

import isTeachersUnitePolicyID from '@libs/isTeachersUnitePolicyID';
import {canSubmitPerDiemExpenseFromWorkspace, getPolicyRole, isGroupPolicy, isPolicyMemberWithoutPendingDelete, isTimeTrackingEnabled} from '@libs/PolicyUtils';
import {canCreateExpensesOnPolicy, canSubmitPerDiemExpenseFromWorkspace, isGroupPolicy, isPolicyMemberWithoutPendingDelete, isTimeTrackingEnabled} from '@libs/PolicyUtils';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
Expand All @@ -22,15 +22,10 @@ function checkForUserPendingDelete(login: string, policy: OnyxEntry<Policy>) {
return isPolicyMemberWithoutPendingDelete(login, policy);
}

function isPolicyMemberByRole(policy: OnyxEntry<Policy>, login: string) {
const role = getPolicyRole(policy, login);
return !!role && (Object.values(CONST.POLICY.ROLE) as string[]).includes(role);
}

function isPolicyValidForMovingExpenses(policy: OnyxEntry<Policy>, login: string, isPerDiemRequest?: boolean, isTimeRequest?: boolean) {
return (
checkForUserPendingDelete(login, policy) &&
isPolicyMemberByRole(policy, login) &&
canCreateExpensesOnPolicy(policy, login) &&
isGroupPolicy(policy) &&
policy?.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE &&
// Teachers Unite doesn't support reimbursement, so it can never be a destination for moving/reporting an expense.
Expand Down
21 changes: 19 additions & 2 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5013,6 +5013,8 @@ ${amount} für ${merchant} – ${date}`,
return 'Personenverwaltung';
case CONST.POLICY.ROLE.PAYMENTS_ADMIN:
return 'Zahlungsadministrator';
case CONST.POLICY.ROLE.APPROVE_ONLY:
return 'Nur Genehmigung';
case CONST.POLICY.ROLE.USER:
return 'Mitglied';
default:
Expand Down Expand Up @@ -5053,6 +5055,7 @@ ${amount} für ${merchant} – ${date}`,
cardAdminAlternateText: 'Arbeitsbereichskarten verwalten.',
peopleAdminAlternateText: 'Mitglieder und Genehmigungsabläufe verwalten.',
paymentsAdminAlternateText: 'Workflow-Zahlungen verwalten.',
approveOnlyAlternateText: 'Nur Berichte genehmigen.',
readOnlyActionTitle: 'Nicht so schnell …',
readOnlyActionPrompt: 'Ihre Arbeitsbereichsrolle kann diese Einstellungen anzeigen, aber nicht bearbeiten.',
noAccessActionPrompt: 'Ihre Arbeitsbereichsrolle hat keinen Zugriff auf diese Einstellungen. Bitten Sie eine/n Admin, falls Sie Zugriff benötigen.',
Expand Down Expand Up @@ -7298,6 +7301,15 @@ Der Control-Tarif beginnt bei 9 $ pro aktivem Mitglied und Monat.`,
},
people: {
genericFailureMessage: 'Beim Entfernen eines Mitglieds aus dem Workspace ist ein Fehler aufgetreten, bitte versuche es erneut',
approveOnlyRoleBlockedTitle: 'Diese Rolle kann nicht geändert werden',
approveOnlyRoleBlockedConfirm: 'Verstanden',
approveOnlyRoleBlockedDescription: 'Mitglieder mit der Rolle „Nur Genehmigung“ können in diesem Arbeitsbereich keine Ausgaben erstellen. Diese müssen zuerst behoben werden:',
approveOnlyRoleBlockedReasons: {
hasCardOnPolicy: 'Ihnen ist in diesem Arbeitsbereich eine Karte zugewiesen. Ein Arbeitsbereichs- oder Kartenadministrator muss sie zuerst entziehen.',
isRestrictedByDomainGroup:
'Dieser Arbeitsbereich ist der bevorzugte Arbeitsbereich für eine Domänengruppe, in der sie sich befinden. Ein Domänenadministrator muss dies zuerst ändern.',
isDefaultPolicy: 'Dies ist ihr Standard-Arbeitsbereich. Sie müssen einen anderen festlegen, bevor Sie ihre Rolle aktualisieren können.',
},
removeMembersPrompt: ({memberName}: {memberName: string}) => ({
one: `Möchtest du ${memberName} wirklich entfernen?`,
other: 'Sind Sie sicher, dass Sie diese Mitglieder entfernen möchten?',
Expand Down Expand Up @@ -7349,6 +7361,10 @@ Der Control-Tarif beginnt bei 9 $ pro aktivem Mitglied und Monat.`,
cannotRemove: 'Du kannst dich selbst oder den/die Workspace-Inhaber:in nicht entfernen',
genericRemove: 'Beim Entfernen dieses Workspace-Mitglieds ist ein Problem aufgetreten',
},
makeApproveOnly: () => ({
one: 'Auf „Nur Genehmigung“ setzen',
other: 'Als „Nur Genehmigung“ festlegen',
}),
addedWithPrimary: 'Einige Mitglieder wurden mit ihren primären Anmeldungen hinzugefügt.',
invitedBySecondaryLogin: (secondaryLogin: string) => `Hinzugefügt durch sekundären Login ${secondaryLogin}.`,
workspaceMembersCount: (count: number) => `Gesamtzahl der Workspace-Mitglieder: ${count}`,
Expand All @@ -7371,6 +7387,7 @@ Der Control-Tarif beginnt bei 9 $ pro aktivem Mitglied und Monat.`,
admins: 'Workspace-Administratoren',
approvers: 'Genehmigende',
auditors: 'Prüfer',
approveOnly: 'Nur Genehmigung',
guests: 'Gäste',
editors: 'Bearbeiter',
emptyRoleFilter: {title: 'Keine Mitglieder entsprechen diesem Filter', subtitle: 'Laden Sie ein Mitglied ein oder ändern Sie den Filter oben.'},
Expand Down Expand Up @@ -9675,8 +9692,8 @@ Fügen Sie weitere Ausgabelimits hinzu, um den Cashflow Ihres Unternehmens zu sc
other: `hat Sie aus den Genehmigungs-Workflows und Spesen-Chats von ${joinedNames} entfernt. Zuvor eingereichte Berichte bleiben in Ihrem Posteingang zur Genehmigung verfügbar.`,
};
},
demotedFromWorkspace: (policyName: string, oldRole: string) =>
`hat Ihre Rolle in ${policyName} von ${oldRole} zu Nutzer geändert. Sie wurden aus allen Einreicher-Spesen-Chats entfernt, außer aus Ihrem eigenen.`,
demotedFromWorkspace: (policyName: string, oldRole: string, newRole: string) =>
`hat Ihre Rolle in ${policyName} von ${oldRole} zu ${newRole} geändert. Sie wurden aus allen Einreicher-Spesen-Chats entfernt, außer aus Ihrem eigenen.`,
updatedWorkspaceCurrencyAction: (oldCurrency: string, newCurrency: string) => `Standardwährung auf ${newCurrency} aktualisiert (zuvor ${oldCurrency})`,
updatedWorkspaceFrequencyAction: (oldFrequency: string, newFrequency: string) => `die automatische Berichtshäufigkeit auf „${newFrequency}“ aktualisiert (zuvor „${oldFrequency}“)`,
updateApprovalMode: (newValue: string, oldValue?: string) => `hat den Genehmigungsmodus auf „${newValue}“ aktualisiert (zuvor „${oldValue}“)`,
Expand Down
Loading
Loading