Skip to content

Commit

Permalink
Merge branch 'main' into ts/improve-form
Browse files Browse the repository at this point in the history
  • Loading branch information
blazejkustra committed Feb 13, 2024
2 parents fe4aace + e90415d commit e11690d
Show file tree
Hide file tree
Showing 53 changed files with 537 additions and 474 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 1001044004
versionName "1.4.40-4"
versionCode 1001044005
versionName "1.4.40-5"
}

flavorDimensions "default"
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.40.4</string>
<string>1.4.40.5</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.40.4</string>
<string>1.4.40.5</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.40</string>
<key>CFBundleVersion</key>
<string>1.4.40.4</string>
<string>1.4.40.5</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.40-4",
"version": "1.4.40-5",
"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: 2 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Onyx from 'react-native-onyx';
import {PickerStateProvider} from 'react-native-picker-select';
import {SafeAreaProvider} from 'react-native-safe-area-context';
import '../wdyr';
import ActiveElementRoleProvider from './components/ActiveElementRoleProvider';
import ActiveWorkspaceContextProvider from './components/ActiveWorkspace/ActiveWorkspaceProvider';
import ColorSchemeWrapper from './components/ColorSchemeWrapper';
import ComposeProviders from './components/ComposeProviders';
Expand Down Expand Up @@ -78,6 +79,7 @@ function App({url}: AppProps) {
PickerStateProvider,
EnvironmentProvider,
CustomStatusBarAndBackgroundContextProvider,
ActiveElementRoleProvider,
ActiveWorkspaceContextProvider,
]}
>
Expand Down
6 changes: 3 additions & 3 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1362,9 +1362,9 @@ const CONST = {
OWNER_EMAIL_FAKE: '_FAKE_',
OWNER_ACCOUNT_ID_FAKE: 0,
REIMBURSEMENT_CHOICES: {
REIMBURSEMENT_YES: 'reimburseYes', // Direct
REIMBURSEMENT_NO: 'reimburseNo', // None
REIMBURSEMENT_MANUAL: 'reimburseManual', // Indirect
REIMBURSEMENT_YES: 'reimburseYes',
REIMBURSEMENT_NO: 'reimburseNo',
REIMBURSEMENT_MANUAL: 'reimburseManual',
},
ID_FAKE: '_FAKE_',
EMPTY: 'EMPTY',
Expand Down
20 changes: 20 additions & 0 deletions src/components/ActiveElementRoleProvider/index.native.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React from 'react';
import type {ActiveElementRoleContextValue, ActiveElementRoleProps} from './types';

const ActiveElementRoleContext = React.createContext<ActiveElementRoleContextValue>({
role: null,
});

function ActiveElementRoleProvider({children}: ActiveElementRoleProps) {
const value = React.useMemo(
() => ({
role: null,
}),
[],
);

return <ActiveElementRoleContext.Provider value={value}>{children}</ActiveElementRoleContext.Provider>;
}

export default ActiveElementRoleProvider;
export {ActiveElementRoleContext};
40 changes: 40 additions & 0 deletions src/components/ActiveElementRoleProvider/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React, {useEffect, useState} from 'react';
import type {ActiveElementRoleContextValue, ActiveElementRoleProps} from './types';

const ActiveElementRoleContext = React.createContext<ActiveElementRoleContextValue>({
role: null,
});

function ActiveElementRoleProvider({children}: ActiveElementRoleProps) {
const [activeRoleRef, setRole] = useState<string | null>(document?.activeElement?.role ?? null);

const handleFocusIn = () => {
setRole(document?.activeElement?.role ?? null);
};

const handleFocusOut = () => {
setRole(null);
};

useEffect(() => {
document.addEventListener('focusin', handleFocusIn);
document.addEventListener('focusout', handleFocusOut);

return () => {
document.removeEventListener('focusin', handleFocusIn);
document.removeEventListener('focusout', handleFocusOut);
};
}, []);

const value = React.useMemo(
() => ({
role: activeRoleRef,
}),
[activeRoleRef],
);

return <ActiveElementRoleContext.Provider value={value}>{children}</ActiveElementRoleContext.Provider>;
}

export default ActiveElementRoleProvider;
export {ActiveElementRoleContext};
9 changes: 9 additions & 0 deletions src/components/ActiveElementRoleProvider/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
type ActiveElementRoleContextValue = {
role: string | null;
};

type ActiveElementRoleProps = {
children: React.ReactNode;
};

export type {ActiveElementRoleContextValue, ActiveElementRoleProps};
18 changes: 10 additions & 8 deletions src/components/Button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -265,14 +265,16 @@ function Button(

return (
<>
<KeyboardShortcutComponent
isDisabled={isDisabled}
isLoading={isLoading}
allowBubble={allowBubble}
onPress={onPress}
pressOnEnter={pressOnEnter}
enterKeyEventListenerPriority={enterKeyEventListenerPriority}
/>
{pressOnEnter && (
<KeyboardShortcutComponent
isDisabled={isDisabled}
isLoading={isLoading}
allowBubble={allowBubble}
onPress={onPress}
pressOnEnter={pressOnEnter}
enterKeyEventListenerPriority={enterKeyEventListenerPriority}
/>
)}
<PressableWithFeedback
ref={ref}
onPress={(event) => {
Expand Down
23 changes: 22 additions & 1 deletion src/components/Form/FormProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import lodashIsEqual from 'lodash/isEqual';
import type {ForwardedRef, MutableRefObject, ReactNode} from 'react';
import React, {createRef, forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState} from 'react';
import React, {createRef, forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react';
import type {NativeSyntheticEvent, StyleProp, TextInputSubmitEditingEventData, ViewStyle} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import * as ValidationUtils from '@libs/ValidationUtils';
import Visibility from '@libs/Visibility';
import * as FormActions from '@userActions/FormActions';
Expand Down Expand Up @@ -92,6 +93,7 @@ function FormProvider(
}: FormProviderProps,
forwardedRef: ForwardedRef<FormRef>,
) {
const {preferredLocale} = useLocalize();
const inputRefs = useRef<InputRefs>({});
const touchedInputs = useRef<Record<string, boolean>>({});
const [inputValues, setInputValues] = useState<Form>(() => ({...draftValues}));
Expand Down Expand Up @@ -159,6 +161,25 @@ function FormProvider(
[errors, formID, validate],
);

// When locales change from another session of the same account,
// validate the form in order to update the error translations
useEffect(() => {
// Return since we only have issues with error translations
if (Object.keys(errors).length === 0) {
return;
}

// Prepare validation values
const trimmedStringValues = ValidationUtils.prepareValues(inputValues);

// Validate in order to make sure the correct error translations are displayed,
// making sure to not clear server errors if they exist
onValidate(trimmedStringValues, !hasServerError);

// Only run when locales change
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [preferredLocale]);

/** @param inputID - The inputID of the input being touched */
const setTouchedInput = useCallback(
(inputID: keyof Form) => {
Expand Down
4 changes: 2 additions & 2 deletions src/components/LHNOptionsList/LHNOptionsList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {FlashList} from '@shopify/flash-list';
import type {ReactElement} from 'react';
import React, {useCallback} from 'react';
import React, {memo, useCallback} from 'react';
import {StyleSheet, View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
import withCurrentReportID from '@components/withCurrentReportID';
Expand Down Expand Up @@ -158,7 +158,7 @@ export default withCurrentReportID(
transactionViolations: {
key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
},
})(LHNOptionsList),
})(memo(LHNOptionsList)),
);

export type {LHNOptionsListProps};
3 changes: 1 addition & 2 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,9 @@ function MoneyReportHeader({session, policy, chatReport, nextStep, report: money
const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(moneyRequestReport, policy);
const isPaidGroupPolicy = ReportUtils.isPaidGroupPolicy(moneyRequestReport);
const isManager = ReportUtils.isMoneyRequestReport(moneyRequestReport) && session?.accountID === moneyRequestReport.managerID;
const isReimburser = session?.email === policy?.reimburserEmail;
const isPayer = isPaidGroupPolicy
? // In a group policy, the admin approver can pay the report directly by skipping the approval step
isReimburser && (isApproved || isManager)
isPolicyAdmin && (isApproved || isManager)
: isPolicyAdmin || (ReportUtils.isMoneyRequestReport(moneyRequestReport) && isManager);
const isDraft = ReportUtils.isDraftExpenseReport(moneyRequestReport);
const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy);
Expand Down
6 changes: 5 additions & 1 deletion src/components/OnyxProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ const [withPersonalDetails, PersonalDetailsProvider, , usePersonalDetails] = cre
const [withCurrentDate, CurrentDateProvider] = createOnyxContext(ONYXKEYS.CURRENT_DATE);
const [withReportActionsDrafts, ReportActionsDraftsProvider] = createOnyxContext(ONYXKEYS.COLLECTION.REPORT_ACTIONS_DRAFTS);
const [withBlockedFromConcierge, BlockedFromConciergeProvider] = createOnyxContext(ONYXKEYS.NVP_BLOCKED_FROM_CONCIERGE);
const [withBetas, BetasProvider, BetasContext] = createOnyxContext(ONYXKEYS.BETAS);
const [withBetas, BetasProvider, BetasContext, useBetas] = createOnyxContext(ONYXKEYS.BETAS);
const [withReportCommentDrafts, ReportCommentDraftsProvider] = createOnyxContext(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT);
const [withPreferredTheme, PreferredThemeProvider, PreferredThemeContext] = createOnyxContext(ONYXKEYS.PREFERRED_THEME);
const [withFrequentlyUsedEmojis, FrequentlyUsedEmojisProvider, , useFrequentlyUsedEmojis] = createOnyxContext(ONYXKEYS.FREQUENTLY_USED_EMOJIS);
const [withPreferredEmojiSkinTone, PreferredEmojiSkinToneProvider, PreferredEmojiSkinToneContext] = createOnyxContext(ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE);
const [, SessionProvider, , useSession] = createOnyxContext(ONYXKEYS.SESSION);

type OnyxProviderProps = {
/** Rendered child component */
Expand All @@ -35,6 +36,7 @@ function OnyxProvider(props: OnyxProviderProps) {
PreferredThemeProvider,
FrequentlyUsedEmojisProvider,
PreferredEmojiSkinToneProvider,
SessionProvider,
]}
>
{props.children}
Expand All @@ -59,8 +61,10 @@ export {
withReportCommentDrafts,
withPreferredTheme,
PreferredThemeContext,
useBetas,
withFrequentlyUsedEmojis,
useFrequentlyUsedEmojis,
withPreferredEmojiSkinTone,
PreferredEmojiSkinToneContext,
useSession,
};
8 changes: 5 additions & 3 deletions src/components/OptionsList/BaseOptionsList.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import isEqual from 'lodash/isEqual';
import type {ForwardedRef} from 'react';
import React, {forwardRef, memo, useEffect, useRef} from 'react';
import React, {forwardRef, memo, useEffect, useMemo, useRef} from 'react';
import type {SectionListRenderItem} from 'react-native';
import {View} from 'react-native';
import OptionRow from '@components/OptionRow';
Expand Down Expand Up @@ -31,7 +31,7 @@ function BaseOptionsList(
shouldHaveOptionSeparator = false,
showTitleTooltip = false,
optionHoveredStyle,
contentContainerStyles,
contentContainerStyles: contentContainerStylesProp,
sectionHeaderStyle,
showScrollIndicator = true,
listContainerStyles: listContainerStylesProp,
Expand All @@ -51,6 +51,7 @@ function BaseOptionsList(
nestedScrollEnabled = true,
bounces = true,
renderFooterContent,
safeAreaPaddingBottomStyle,
}: BaseOptionListProps,
ref: ForwardedRef<OptionsList>,
) {
Expand All @@ -64,7 +65,8 @@ function BaseOptionsList(
const previousSections = usePrevious<OptionsListData[]>(sections);
const didLayout = useRef(false);

const listContainerStyles = listContainerStylesProp ?? [styles.flex1];
const listContainerStyles = useMemo(() => listContainerStylesProp ?? [styles.flex1], [listContainerStylesProp, styles.flex1]);
const contentContainerStyles = useMemo(() => [safeAreaPaddingBottomStyle, contentContainerStylesProp], [contentContainerStylesProp, safeAreaPaddingBottomStyle]);

/**
* This helper function is used to memoize the computation needed for getItemLayout. It is run whenever section data changes.
Expand Down
2 changes: 1 addition & 1 deletion src/components/OptionsList/index.native.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ function OptionsList(props: OptionsListProps, ref: ForwardedRef<OptionsListType>
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
ref={ref}
onScrollBeginDrag={() => Keyboard.dismiss()}
onScrollBeginDrag={Keyboard.dismiss}
/>
);
}
Expand Down
3 changes: 1 addition & 2 deletions src/components/ReportActionItem/ReportPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ function ReportPreview({
const {totalDisplaySpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(iouReport);
const policyType = policy?.type;
const isAutoReimbursable = ReportUtils.canBeAutoReimbursed(iouReport, policy);
const isReimburser = session?.email === policy?.reimburserEmail;

const iouSettled = ReportUtils.isSettled(iouReportID);
const iouCanceled = ReportUtils.isArchivedRoom(chatReport);
Expand Down Expand Up @@ -213,7 +212,7 @@ function ReportPreview({
const isPolicyAdmin = policyType !== CONST.POLICY.TYPE.PERSONAL && policy?.role === CONST.POLICY.ROLE.ADMIN;
const isPayer = isPaidGroupPolicy
? // In a paid group policy, the admin approver can pay the report directly by skipping the approval step
isReimburser && (isApproved || isCurrentUserManager)
isPolicyAdmin && (isApproved || isCurrentUserManager)
: isPolicyAdmin || (isMoneyRequestReport && isCurrentUserManager);
const isOnInstantSubmitPolicy = PolicyUtils.isInstantSubmitEnabled(policy);
const isOnSubmitAndClosePolicy = PolicyUtils.isSubmitAndClose(policy);
Expand Down
Loading

0 comments on commit e11690d

Please sign in to comment.