Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…y-App into audit/app-startup/use-only-required-locales
  • Loading branch information
hurali97 committed Apr 8, 2024
2 parents 3304c9b + f7b000b commit 9a6f88f
Show file tree
Hide file tree
Showing 70 changed files with 1,763 additions and 1,041 deletions.
4 changes: 2 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
multiDexEnabled rootProject.ext.multiDexEnabled
versionCode 1001046009
versionName "1.4.60-9"
versionCode 1001046013
versionName "1.4.60-13"
resConfigs "en", "es"
}

Expand Down
11 changes: 10 additions & 1 deletion assets/images/avatars/fallback-avatar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
55 changes: 55 additions & 0 deletions docs/articles/expensify-classic/expenses/Expense-Rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
---
title: Expense Rules
description: Expense rules allow you to automatically categorize, tag, and report expenses based on the merchant's name.

---
# Overview
Expense rules allow you to automatically categorize, tag, and report expenses based on the merchant’s name.

# How to use Expense Rules
**To create an expense rule, follow these steps:**
1. Navigate to **Settings > Account > Expense Rules**
2. Click on **New Rule**
3. Fill in the required information to set up your rule

When creating an expense rule, you will be able to apply the following rules to expenses:

![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_01.png){:width="100%"}

- **Merchant:** Updates the merchant name, e.g., “Starbucks #238” could be changed to “Starbucks”
- **Category:** Applies a workspace category to the expense
- **Tag:** Applies a tag to the expense, e.g., a Department or Location
- **Description:** Adds a description to the description field on the expense
- **Reimbursability:** Determines whether the expense will be marked as reimbursable or non-reimbursable
- **Billable**: Determines whether the expense is billable
- **Add to a report named:** Adds the expense to a report with the name you type into the field. If no report with that name exists, a new report will be created

## Tips on using Expense Rules
- If you'd like to apply a rule to all expenses (“Universal Rule”) rather than just one merchant, simply enter a period [.] and nothing else into the **“When the merchant name contains:”** field. **Note:** Universal Rules will always take precedence over all other rules for category (more on this below).
- You can apply a rule to previously entered expenses by checking the **Apply to existing matching expenses** checkbox. Click “Preview Matching Expenses” to see if your rule matches the intended expenses.
- You can create expense rules while editing an expense. To do this, simply check the box **“Create a rule based on your changes"** at the time of editing. Note that the expense must be saved, reopened, and edited for this option to appear.


![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_02.png){:width="100%"}


To delete an expense rule, go to **Settings > Account > Expense Rules**, scroll down to the rule you’d like to remove, and then click the trash can icon in the upper right corner of the rule:

![Insert alt text for accessibility here](https://help.expensify.com/assets/images/ExpensifyHelp_ExpenseRules_03.png){:width="100%"}

# Deep Dive
In general, your expense rules will be applied in order, from **top to bottom**, i.e., from the first rule. However, other settings can impact how expense rules are applied. Here is the hierarchy that determines how these are applied:
1. A Universal Rule will **always** precede over any other expense category rules. Rules that would otherwise change the expense category will **not** override the Universal Rule.
2. If Scheduled Submit and the setting “Enforce Default Report Title” are enabled on the workspace, this will take precedence over any rules trying to add the expense to a report.
3. If the expense is from a Company Card that is forced to a workspace with strict rule enforcement, those rules will take precedence over individual expense rules.
4. If you belong to a workspace that is tied to an accounting integration, the configuration settings for this connection may update your expense details upon export, even if the expense rules were successfully applied to the expense.


{% include faq-begin.md %}
## How can I use Expense Rules to vendor match when exporting to an accounting package?
When exporting non-reimbursable expenses to your connected accounting package, the payee field will list "Credit Card Misc." if the merchant name on the expense in Expensify is not an exact match to a vendor in the accounting package.
When an exact match is unavailable, "Credit Card Misc." prevents multiple variations of the same vendor (e.g., Starbucks and Starbucks #1234, as is often seen in credit card statements) from being created in your accounting package.
For repeated expenses, the best practice is to use Expense Rules, which will automatically update the merchant name without having to do it manually each time.
This only works for connections to QuickBooks Online, Desktop, and Xero. Vendor matching cannot be performed in this manner for NetSuite or Sage Intacct due to limitations in the API of the accounting package.

{% include faq-end.md %}
3 changes: 3 additions & 0 deletions docs/redirects.csv
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,6 @@ https://help.expensify.com/articles/expensify-classic/workspace-and-domain-setti
https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/Reimbursement,https://help.expensify.com/articles/expensify-classic/send-payments/Reimbursing-Reports
https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/Tags,https://help.expensify.com/articles/expensify-classic/workspaces/Tags
https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/User-Roles,https://help.expensify.com/articles/expensify-classic/workspaces/Change-member-workspace-roles
https://help.expensify.com/articles/expensify-classic/workspace-and-domain-settings/tax-tracking,https://help.expensify.com/articles/expensify-classic/expenses/expenses/Apply-Tax
https://help.expensify.com/articles/expensify-classic/copilots-and-delegates/User-Roles.html,https://help.expensify.com/expensify-classic/hubs/copilots-and-delegates/
https://help.expensify.com/articles/expensify-classic/expensify-billing/Billing-Owner,https://help.expensify.com/articles/expensify-classic/workspaces/Assign-billing-owner-and-payment-account
7 changes: 6 additions & 1 deletion ios/NewExpensify/AppDelegate.mm
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ - (BOOL)application:(UIApplication *)application
// stopped by a native module in the JS so we can measure total time starting
// in the native layer and ending in the JS layer.
[RCTStartupTimer start];


if (![[NSUserDefaults standardUserDefaults] boolForKey:@"isFirstRunComplete"]) {
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"isFirstRunComplete"];
}

return YES;
}

Expand Down
2 changes: 1 addition & 1 deletion ios/NewExpensify/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>1.4.60.9</string>
<string>1.4.60.13</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSApplicationQueriesSchemes</key>
Expand Down
2 changes: 1 addition & 1 deletion ios/NewExpensifyTests/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.4.60.9</string>
<string>1.4.60.13</string>
</dict>
</plist>
2 changes: 1 addition & 1 deletion ios/NotificationServiceExtension/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<key>CFBundleShortVersionString</key>
<string>1.4.60</string>
<key>CFBundleVersion</key>
<string>1.4.60.9</string>
<string>1.4.60.13</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "new.expensify",
"version": "1.4.60-9",
"version": "1.4.60-13",
"author": "Expensify, Inc.",
"homepage": "https://new.expensify.com",
"description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.",
Expand Down
2 changes: 0 additions & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import HTMLEngineProvider from './components/HTMLEngineProvider';
import InitialURLContextProvider from './components/InitialURLContextProvider';
import {LocaleContextProvider} from './components/LocaleContextProvider';
import OnyxProvider from './components/OnyxProvider';
import OptionsListContextProvider from './components/OptionListContextProvider';
import PopoverContextProvider from './components/PopoverProvider';
import SafeArea from './components/SafeArea';
import ScrollOffsetContextProvider from './components/ScrollOffsetContextProvider';
Expand Down Expand Up @@ -83,7 +82,6 @@ function App({url}: AppProps) {
FullScreenContextProvider,
VolumeContextProvider,
VideoPopoverMenuContextProvider,
OptionsListContextProvider,
]}
>
<CustomStatusBarAndBackground />
Expand Down
5 changes: 5 additions & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,11 @@ const CONST = {
RIGHT: 'right',
},
POPOVER_MENU_PADDING: 8,
RESTORE_FOCUS_TYPE: {
DEFAULT: 'default',
DELETE: 'delete',
PRESERVE: 'preserve',
},
},
TIMING: {
CALCULATE_MOST_RECENT_LAST_MODIFIED_ACTION: 'calc_most_recent_last_modified_action',
Expand Down
28 changes: 14 additions & 14 deletions src/components/Avatar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,26 @@ function Avatar({
setImageError(false);
}, [source]);

if (!source) {
return null;
}

const isWorkspace = type === CONST.ICON_TYPE_WORKSPACE;
const iconSize = StyleUtils.getAvatarSize(size);

const imageStyle: StyleProp<ImageStyle> = [StyleUtils.getAvatarStyle(size), imageStyles, styles.noBorderRadius];
const iconStyle = imageStyles ? [StyleUtils.getAvatarStyle(size), styles.bgTransparent, imageStyles] : undefined;

const iconFillColor = isWorkspace ? StyleUtils.getDefaultWorkspaceAvatarColor(name).fill : fill;
// We pass the color styles down to the SVG for the workspace and fallback avatar.
const useFallBackAvatar = imageError || source === Expensicons.FallbackAvatar || !source;
const fallbackAvatar = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatar(name) : fallbackIcon || Expensicons.FallbackAvatar;
const fallbackAvatarTestID = isWorkspace ? ReportUtils.getDefaultWorkspaceAvatarTestID(name) : fallbackIconTestID || 'SvgFallbackAvatar Icon';

const avatarSource = imageError ? fallbackAvatar : source;
const avatarSource = useFallBackAvatar ? fallbackAvatar : source;

let iconColors;
if (isWorkspace) {
iconColors = StyleUtils.getDefaultWorkspaceAvatarColor(name);
} else if (useFallBackAvatar) {
iconColors = StyleUtils.getBackgroundColorAndFill(theme.border, theme.icon);
} else {
iconColors = null;
}

return (
<View style={[containerStyles, styles.pointerEventsNone]}>
Expand All @@ -107,13 +112,8 @@ function Avatar({
src={avatarSource}
height={iconSize}
width={iconSize}
fill={imageError ? theme.offline : iconFillColor}
additionalStyles={[
StyleUtils.getAvatarBorderStyle(size, type),
isWorkspace && StyleUtils.getDefaultWorkspaceAvatarColor(name),
imageError && StyleUtils.getBackgroundColorStyle(theme.fallbackIconColor),
iconAdditionalStyles,
]}
fill={imageError ? iconColors?.fill ?? theme.offline : iconColors?.fill ?? fill}
additionalStyles={[StyleUtils.getAvatarBorderStyle(size, type), iconColors, iconAdditionalStyles]}
/>
</View>
)}
Expand Down
21 changes: 14 additions & 7 deletions src/components/Composer/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -388,13 +388,20 @@ function Composer(
disabled={isDisabled}
onKeyPress={handleKeyPress}
onFocus={(e) => {
ReportActionComposeFocusManager.onComposerFocus(() => {
if (!textInput.current) {
return;
}

textInput.current.focus();
});
if (isReportActionCompose) {
ReportActionComposeFocusManager.onComposerFocus(null);
} else {
// While a user edits a comment, if they open the LHN menu, we want to ensure that
// the focus returns to the message edit composer after they click on a menu item (e.g. mark as read).
// To achieve this, we re-assign the focus callback here.
ReportActionComposeFocusManager.onComposerFocus(() => {
if (!textInput.current) {
return;
}

textInput.current.focus();
});
}

props.onFocus?.(e);
}}
Expand Down
2 changes: 2 additions & 0 deletions src/components/EmojiPicker/EmojiPicker.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ const EmojiPicker = forwardRef((props, ref) => {
anchorDimensions={emojiAnchorDimension.current}
avoidKeyboard
shoudSwitchPositionIfOverflow
shouldEnableNewFocusManagement
restoreFocusType={CONST.MODAL.RESTORE_FOCUS_TYPE.DELETE}
>
<EmojiPickerMenu
onEmojiSelected={selectEmoji}
Expand Down
35 changes: 23 additions & 12 deletions src/components/Modal/BaseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import useNativeDriver from '@libs/useNativeDriver';
import variables from '@styles/variables';
import * as Modal from '@userActions/Modal';
import CONST from '@src/CONST';
import ModalContent from './ModalContent';
import type BaseModalProps from './types';

function BaseModal(
Expand Down Expand Up @@ -42,6 +43,8 @@ function BaseModal(
children,
shouldUseCustomBackdrop = false,
onBackdropPress,
shouldEnableNewFocusManagement = false,
restoreFocusType,
}: BaseModalProps,
ref: React.ForwardedRef<View>,
) {
Expand All @@ -56,6 +59,14 @@ function BaseModal(
const isVisibleRef = useRef(isVisible);
const wasVisible = usePrevious(isVisible);

const modalId = useMemo(() => ComposerFocusManager.getId(), []);
const saveFocusState = () => {
if (shouldEnableNewFocusManagement) {
ComposerFocusManager.saveFocusState(modalId);
}
ComposerFocusManager.resetReadyToFocus(modalId);
};

/**
* Hides modal
* @param callHideCallback - Should we call the onModalHide callback
Expand All @@ -70,11 +81,9 @@ function BaseModal(
onModalHide();
}
Modal.onModalDidClose();
if (!fullscreen) {
ComposerFocusManager.setReadyToFocus();
}
ComposerFocusManager.refocusAfterModalFullyClosed(modalId, restoreFocusType);
},
[shouldSetModalVisibility, onModalHide, fullscreen],
[shouldSetModalVisibility, onModalHide, restoreFocusType, modalId],
);

useEffect(() => {
Expand Down Expand Up @@ -126,7 +135,7 @@ function BaseModal(
};

const handleDismissModal = () => {
ComposerFocusManager.setReadyToFocus();
ComposerFocusManager.setReadyToFocus(modalId);
};

const {
Expand Down Expand Up @@ -190,7 +199,7 @@ function BaseModal(
onModalShow={handleShowModal}
propagateSwipe={propagateSwipe}
onModalHide={hideModal}
onModalWillShow={() => ComposerFocusManager.resetReadyToFocus()}
onModalWillShow={saveFocusState}
onDismiss={handleDismissModal}
onSwipeComplete={() => onClose?.()}
swipeDirection={swipeDirection}
Expand All @@ -214,12 +223,14 @@ function BaseModal(
avoidKeyboard={avoidKeyboard}
customBackdrop={shouldUseCustomBackdrop ? <Overlay onPress={handleBackdropPress} /> : undefined}
>
<View
style={[styles.defaultModalContainer, modalPaddingStyles, modalContainerStyle, !isVisible && styles.pointerEventsNone]}
ref={ref}
>
<ColorSchemeWrapper>{children}</ColorSchemeWrapper>
</View>
<ModalContent onDismiss={handleDismissModal}>
<View
style={[styles.defaultModalContainer, modalPaddingStyles, modalContainerStyle, !isVisible && styles.pointerEventsNone]}
ref={ref}
>
<ColorSchemeWrapper>{children}</ColorSchemeWrapper>
</View>
</ModalContent>
</ReactNativeModal>
);
}
Expand Down
23 changes: 23 additions & 0 deletions src/components/Modal/ModalContent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type {ReactNode} from 'react';
import React from 'react';

type ModalContentProps = {
/** Modal contents */
children: ReactNode;

/**
* Callback method fired after modal content is unmounted.
* isVisible is not enough to cover all modal close cases,
* such as closing the attachment modal through the browser's back button.
* */
onDismiss: () => void;
};

function ModalContent({children, onDismiss = () => {}}: ModalContentProps) {
// eslint-disable-next-line react-hooks/exhaustive-deps
React.useEffect(() => () => onDismiss?.(), []);
return children;
}
ModalContent.displayName = 'ModalContent';

export default ModalContent;
10 changes: 0 additions & 10 deletions src/components/Modal/index.android.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,7 @@
import React from 'react';
import {AppState} from 'react-native';
import ComposerFocusManager from '@libs/ComposerFocusManager';
import BaseModal from './BaseModal';
import type BaseModalProps from './types';

AppState.addEventListener('focus', () => {
ComposerFocusManager.setReadyToFocus();
});

AppState.addEventListener('blur', () => {
ComposerFocusManager.resetReadyToFocus();
});

// Only want to use useNativeDriver on Android. It has strange flashes issue on IOS
// https://github.com/react-native-modal/react-native-modal#the-modal-flashes-in-a-weird-way-when-animating
function Modal({useNativeDriver = true, ...rest}: BaseModalProps) {
Expand Down
Loading

0 comments on commit 9a6f88f

Please sign in to comment.