Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/main' into snyk-fix-0d7372eacd38…
Browse files Browse the repository at this point in the history
…c5cb2c35b138334ece99
  • Loading branch information
cristipaval committed Apr 8, 2024
2 parents 9565ca8 + ce06355 commit 5e2f25c
Show file tree
Hide file tree
Showing 45 changed files with 845 additions and 576 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 1001046012
versionName "1.4.60-12"
versionCode 1001046013
versionName "1.4.60-13"
}

flavorDimensions "default"
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.
63 changes: 63 additions & 0 deletions docs/articles/expensify-classic/workspaces/Currency.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
title: Report Currency
description: Understanding expense and report currency
---

# Overview
As a workspace admin, you can choose a default currency for your employees' expense reports, and we’ll automatically convert any expenses into that currency.

Here are a few essential things to remember:

- Currency settings for a workspace apply to all expenses under that workspace. If you need different default currencies for certain employees, creating separate workspaces and configuring the currency settings is best.
- As an admin, the currency settings you establish in the workspace will take precedence over any currency settings individual users may have in their accounts.
- Currency is a workspace-level setting, meaning the currency you set will determine the currency for all expenses submitted on that workspace.

# How to select the currency on a workspace

## As an admin on a group workspace

1. Sign into your Expensify web account
2. Go to **Settings > Workspaces > Group > _[Workspace Name]_> Reports > Report Basics**
3. Adjust the **Report Output Currency**

## On an individual workspace

1. Sign into your Expensify web account
2. Go to **Settings > Workspaces > Individual >_[Workspace Name]_> Reports > Report Basics**
3. Adjust the **Report Output Currency**

Please note the currency setting on an individual workspace is overridden when you submit a report on a group workspace.

# Deep Dive

## Conversion Rates

Using data from Open Exchange Rates, Expensify takes the average rate on the day the expense occurred to convert an expense from one currency to another. The conversion rate can vary depending on when the expense happened since the rate is determined after the market closes on that specific date.

If the markets aren’t open on the day the expense takes place (i.e., on a Saturday), Expensify will use the daily average rate from the last available market day before the purchase took place.

When an expense is logged for a future date, possibly to anticipate a purchase that has yet to occur, we'll use the most recent available data. This means the report's value may change up to the day of that expense.

## Managing expenses for employees in several different countries

Suppose you have employees scattered across the globe who submit expense reports in various currencies. The best way to manage those expenses is to create separate group workspaces for each location or region where your employees are based.

Then, set the default currency for that workspace to match the currency in which the employees are reimbursed.

For example, if you have employees in the US, France, Japan, and India, you’d want to create four separate workspaces, add the employees to each, and then set the corresponding currency for each workspace.

{% include faq-begin.md %}

## I have expenses in several different currencies. How will this show up on a report?

If you're traveling to foreign countries during a reporting period and making purchases in various currencies, each expense is imported with the currency of the purchase.

On your expense report, Expensify will automatically convert each expense to the default currency set for the group workspace.

## How does the currency of an expense impact the conversion rate?

Expenses entered in a foreign currency are automatically converted to the default currency on your workspace. The conversion uses the day’s average trading rate pulled from [Open Exchange Rates](https://openexchangerates.org/).

If you want to bypass the exchange rate conversion, you can manually enter an expense in your default currency instead.

{% include faq-end.md %}
1 change: 1 addition & 0 deletions docs/redirects.csv
Original file line number Diff line number Diff line change
Expand Up @@ -79,3 +79,4 @@ https://help.expensify.com/articles/expensify-classic/workspace-and-domain-setti
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
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.12</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.12</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.12</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-12",
"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
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/ReportActionItem/MoneyReportView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import Navigation from '@libs/Navigation/Navigation';
import * as ReportUtils from '@libs/ReportUtils';
import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground';
import variables from '@styles/variables';
import * as reportActions from '@src/libs/actions/Report';
import ROUTES from '@src/ROUTES';
import type {Policy, PolicyReportField, Report} from '@src/types/onyx';

Expand Down Expand Up @@ -81,6 +82,7 @@ function MoneyReportView({report, policy, shouldShowHorizontalRule}: MoneyReport
errors={report.errorFields?.[fieldKey]}
errorRowStyles={styles.ph5}
key={`menuItem-${fieldKey}`}
onClose={() => reportActions.clearReportFieldErrors(report.reportID, reportField)}
>
<MenuItemWithTopDescription
description={Str.UCFirst(reportField.name)}
Expand Down
9 changes: 9 additions & 0 deletions src/components/SelectionList/BaseListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ function BaseListItem<TItem extends ListItem>({
</View>
</View>
)}
{!item.isSelected && !!item.brickRoadIndicator && (
<View style={[styles.alignItemsCenter, styles.justifyContentCenter]}>
<Icon
src={Expensicons.DotIndicator}
fill={item.brickRoadIndicator === CONST.BRICK_ROAD_INDICATOR_STATUS.INFO ? theme.iconSuccessFill : theme.danger}
/>
</View>
)}

{rightHandSideComponentRender()}
</View>
{FooterComponent}
Expand Down
25 changes: 19 additions & 6 deletions src/components/SelectionList/BaseSelectionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ function BaseSelectionList<TItem extends ListItem>(
textInputRef,
headerMessageStyle,
shouldHideListOnInitialRender = true,
textInputIconLeft,
sectionTitleStyles,
textInputAutoFocus = true,
}: BaseSelectionListProps<TItem>,
ref: ForwardedRef<SelectionListHandle>,
) {
Expand All @@ -79,7 +82,7 @@ function BaseSelectionList<TItem extends ListItem>(
const listRef = useRef<RNSectionList<TItem, SectionWithIndexOffset<TItem>>>(null);
const innerTextInputRef = useRef<RNTextInput | null>(null);
const focusTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const shouldShowTextInput = !!textInputLabel;
const shouldShowTextInput = !!textInputLabel || !!textInputIconLeft;
const shouldShowSelectAll = !!onSelectAll;
const activeElementRole = useActiveElementRole();
const isFocused = useIsFocused();
Expand Down Expand Up @@ -310,7 +313,7 @@ function BaseSelectionList<TItem extends ListItem>(
// We do this so that we can reference the height in `getItemLayout` –
// we need to know the heights of all list items up-front in order to synchronously compute the layout of any given list item.
// So be aware that if you adjust the content of the section header (for example, change the font size), you may need to adjust this explicit height as well.
<View style={[styles.optionsListSectionHeader, styles.justifyContentCenter]}>
<View style={[styles.optionsListSectionHeader, styles.justifyContentCenter, sectionTitleStyles]}>
<Text style={[styles.ph4, styles.textLabelSupporting]}>{section.title}</Text>
</View>
);
Expand Down Expand Up @@ -377,6 +380,9 @@ function BaseSelectionList<TItem extends ListItem>(
/** Focuses the text input when the component comes into focus and after any navigation animations finish. */
useFocusEffect(
useCallback(() => {
if (!textInputAutoFocus) {
return;
}
if (shouldShowTextInput) {
focusTimeoutRef.current = setTimeout(() => {
if (!innerTextInputRef.current) {
Expand All @@ -391,7 +397,7 @@ function BaseSelectionList<TItem extends ListItem>(
}
clearTimeout(focusTimeoutRef.current);
};
}, [shouldShowTextInput]),
}, [shouldShowTextInput, textInputAutoFocus]),
);

const prevTextInputValue = usePrevious(textInputValue);
Expand Down Expand Up @@ -494,8 +500,12 @@ function BaseSelectionList<TItem extends ListItem>(
return;
}

// eslint-disable-next-line no-param-reassign
textInputRef.current = element as RNTextInput;
if (typeof textInputRef === 'function') {
textInputRef(element as RNTextInput);
} else {
// eslint-disable-next-line no-param-reassign
textInputRef.current = element as RNTextInput;
}
}}
label={textInputLabel}
accessibilityLabel={textInputLabel}
Expand All @@ -508,14 +518,17 @@ function BaseSelectionList<TItem extends ListItem>(
inputMode={inputMode}
selectTextOnFocus
spellCheck={false}
iconLeft={textInputIconLeft}
onSubmitEditing={selectFocusedOption}
blurOnSubmit={!!flattenedSections.allOptions.length}
isLoading={isLoadingNewOptions}
testID="selection-list-text-input"
/>
</View>
)}
{!!headerMessage && (
{/* If we are loading new options we will avoid showing any header message. This is mostly because one of the header messages says there are no options. */}
{/* This is misleading because we might be in the process of loading fresh options from the server. */}
{!isLoadingNewOptions && !!headerMessage && (
<View style={headerMessageStyle ?? [styles.ph5, styles.pb5]}>
<Text style={[styles.textLabel, styles.colorMuted]}>{headerMessage}</Text>
</View>
Expand Down
Loading

0 comments on commit 5e2f25c

Please sign in to comment.