Skip to content

Commit

Permalink
Merge branch 'main' into refactor-listitems
Browse files Browse the repository at this point in the history
  • Loading branch information
shubham1206agra committed Apr 1, 2024
2 parents eeda152 + 877f867 commit 9f112ca
Show file tree
Hide file tree
Showing 16 changed files with 165 additions and 107 deletions.
2 changes: 1 addition & 1 deletion contributingGuides/HOW_TO_BECOME_A_CONTRIBUTOR_PLUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,4 @@ C+ are contributors who are experienced at working with Expensify and have gaine

## How to join?

Email contributors@expensify.com and include "C+ Team Application" in the subject line if you’re interested in joining.
Email contributors@expensify.com and include "C+ Team Application" in the subject line if you’re interested in joining. Please include your GitHub username and a link to the PRs you've authored that have been merged. ie. `https://github.com/Expensify/App/pulls?q=is%3Apr+author%3Aparasharrajat+is%3Amerged`
6 changes: 3 additions & 3 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
Expand Up @@ -101,7 +101,7 @@
"date-fns-tz": "^2.0.0",
"dom-serializer": "^0.2.2",
"domhandler": "^4.3.0",
"expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#f7efbd084536c140e65b49cd15f67ad8a2a10675",
"expensify-common": "git+ssh://git@github.com/Expensify/expensify-common.git#1247a822328011083a13abc769ba1911c3586338",
"expo": "^50.0.3",
"expo-av": "~13.10.4",
"expo-image": "1.11.0",
Expand Down
25 changes: 23 additions & 2 deletions src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ function AnchorRenderer({tnode, style, key}: AnchorRendererProps) {
);
}

const hasStrikethroughStyle = 'textDecorationLine' in parentStyle && parentStyle.textDecorationLine === 'line-through';
const textDecorationLineStyle = hasStrikethroughStyle ? styles.underlineLineThrough : {};

return (
<AnchorForCommentsOnly
href={attrHref}
Expand All @@ -65,12 +68,30 @@ function AnchorRenderer({tnode, style, key}: AnchorRendererProps) {
// eslint-disable-next-line react/jsx-props-no-multi-spaces
target={htmlAttribs.target || '_blank'}
rel={htmlAttribs.rel || 'noopener noreferrer'}
style={[style, parentStyle, styles.textUnderlinePositionUnder, styles.textDecorationSkipInkNone]}
style={[style, parentStyle, textDecorationLineStyle, styles.textUnderlinePositionUnder, styles.textDecorationSkipInkNone]}
key={key}
// Only pass the press handler for internal links. For public links or whitelisted internal links fallback to default link handling
onPress={internalNewExpensifyPath || internalExpensifyPath ? () => Link.openLink(attrHref, environmentURL, isAttachment) : undefined}
>
<TNodeChildrenRenderer tnode={tnode} />
<TNodeChildrenRenderer
tnode={tnode}
renderChild={(props) => {
if (props.childTnode.tagName === 'br') {
return <Text key={props.key}>{'\n'}</Text>;
}
if (props.childTnode.type === 'text') {
return (
<Text
key={props.key}
style={[props.childTnode.getNativeStyles(), parentStyle, textDecorationLineStyle, styles.textUnderlinePositionUnder, styles.textDecorationSkipInkNone]}
>
{props.childTnode.data}
</Text>
);
}
return props.childElement;
}}
/>
</AnchorForCommentsOnly>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona
const htmlAttributeAccountID = tnode.attributes.accountid;

let accountID: number;
let displayNameOrLogin: string;
let mentionDisplayText: string;
let navigationRoute: Route;

const tnodeClone = cloneDeep(tnode);

const getMentionDisplayText = (displayText: string, userAccountID: string, userLogin = '') => {
const getShortMentionIfFound = (displayText: string, userAccountID: string, userLogin = '') => {
// If the userAccountID does not exist, this is an email-based mention so the displayText must be an email.
// If the userAccountID exists but userLogin is different from displayText, this means the displayText is either user display name, Hidden, or phone number, in which case we should return it as is.
if (userAccountID && userLogin !== displayText) {
Expand All @@ -59,18 +59,18 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona
if (!isEmpty(htmlAttribAccountID)) {
const user = personalDetails[htmlAttribAccountID];
accountID = parseInt(htmlAttribAccountID, 10);
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
displayNameOrLogin = PersonalDetailsUtils.getDisplayNameOrDefault(user, LocalePhoneNumber.formatPhoneNumber(user?.login ?? ''));
mentionDisplayText = LocalePhoneNumber.formatPhoneNumber(user?.login ?? '') || PersonalDetailsUtils.getDisplayNameOrDefault(user);
mentionDisplayText = getShortMentionIfFound(mentionDisplayText, htmlAttributeAccountID, user?.login ?? '');
navigationRoute = ROUTES.PROFILE.getRoute(htmlAttribAccountID);
} else if ('data' in tnodeClone && !isEmptyObject(tnodeClone.data)) {
// We need to remove the LTR unicode and leading @ from data as it is not part of the login
displayNameOrLogin = tnodeClone.data.replace(CONST.UNICODE.LTR, '').slice(1);
mentionDisplayText = tnodeClone.data.replace(CONST.UNICODE.LTR, '').slice(1);
// We need to replace tnode.data here because we will pass it to TNodeChildrenRenderer below
asMutable(tnodeClone).data = tnodeClone.data.replace(displayNameOrLogin, Str.removeSMSDomain(getMentionDisplayText(displayNameOrLogin, htmlAttributeAccountID)));
asMutable(tnodeClone).data = tnodeClone.data.replace(mentionDisplayText, Str.removeSMSDomain(getShortMentionIfFound(mentionDisplayText, htmlAttributeAccountID)));

accountID = PersonalDetailsUtils.getAccountIDsByLogins([displayNameOrLogin])?.[0];
navigationRoute = ROUTES.DETAILS.getRoute(displayNameOrLogin);
displayNameOrLogin = Str.removeSMSDomain(displayNameOrLogin);
accountID = PersonalDetailsUtils.getAccountIDsByLogins([mentionDisplayText])?.[0];
navigationRoute = ROUTES.DETAILS.getRoute(mentionDisplayText);
mentionDisplayText = Str.removeSMSDomain(mentionDisplayText);
} else {
// If neither an account ID or email is provided, don't render anything
return null;
Expand All @@ -97,7 +97,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona
<UserDetailsTooltip
accountID={accountID}
fallbackUserDetails={{
displayName: displayNameOrLogin,
displayName: mentionDisplayText,
}}
>
<Text
Expand All @@ -108,7 +108,7 @@ function MentionUserRenderer({style, tnode, TDefaultRenderer, currentUserPersona
testID="span"
href={`/${navigationRoute}`}
>
{htmlAttribAccountID ? `@${displayNameOrLogin}` : <TNodeChildrenRenderer tnode={tnodeClone} />}
{htmlAttribAccountID ? `@${mentionDisplayText}` : <TNodeChildrenRenderer tnode={tnodeClone} />}
</Text>
</UserDetailsTooltip>
</Text>
Expand Down
16 changes: 10 additions & 6 deletions src/components/ReportActionItem/ReportPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ function ReportPreview({

const isApproved = ReportUtils.isReportApproved(iouReport);
const canAllowSettlement = ReportUtils.hasUpdatedTotal(iouReport);
const allTransactions = TransactionUtils.getAllReportTransactions(iouReportID);
const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID);
const numberOfScanningReceipts = transactionsWithReceipts.filter((transaction) => TransactionUtils.isReceiptBeingScanned(transaction)).length;
const numberOfPendingRequests = transactionsWithReceipts.filter((transaction) => TransactionUtils.isPending(transaction) && TransactionUtils.isCardTransaction(transaction)).length;
Expand All @@ -138,14 +139,16 @@ function ReportPreview({
const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3);
const lastThreeReceipts = lastThreeTransactionsWithReceipts.map((transaction) => ReceiptUtils.getThumbnailAndImageURIs(transaction));

let formattedMerchant = numberOfRequests === 1 && hasReceipts ? TransactionUtils.getMerchant(transactionsWithReceipts[0]) : null;
let formattedMerchant = numberOfRequests === 1 ? TransactionUtils.getMerchant(allTransactions[0]) : null;
const formattedDescription = numberOfRequests === 1 ? TransactionUtils.getDescription(allTransactions[0]) : null;

if (TransactionUtils.isPartialMerchant(formattedMerchant ?? '')) {
formattedMerchant = null;
}
const previewSubtitle =
// Formatted merchant can be an empty string
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
formattedMerchant ||
(formattedMerchant ?? formattedDescription) ||
translate('iou.requestCount', {
count: numberOfRequests - numberOfScanningReceipts - numberOfPendingRequests,
scanningReceipts: numberOfScanningReceipts,
Expand Down Expand Up @@ -222,13 +225,14 @@ function ReportPreview({
/*
Show subtitle if at least one of the money requests is not being smart scanned, and either:
- There is more than one money request – in this case, the "X requests, Y scanning" subtitle is shown;
- There is only one money request, it has a receipt and is not being smart scanned – in this case, the request merchant is shown;
- There is only one money request, it has a receipt and is not being smart scanned – in this case, the request merchant or description is shown;
* There is an edge case when there is only one distance request with a pending route and amount = 0.
In this case, we don't want to show the merchant because it says: "Pending route...", which is already displayed in the amount field.
In this case, we don't want to show the merchant or description because it says: "Pending route...", which is already displayed in the amount field.
*/
const shouldShowSingleRequestMerchant = numberOfRequests === 1 && !!formattedMerchant && !(hasOnlyTransactionsWithPendingRoutes && !totalDisplaySpend);
const shouldShowSubtitle = !isScanning && (shouldShowSingleRequestMerchant || numberOfRequests > 1);
const shouldShowSingleRequestMerchantOrDescription =
numberOfRequests === 1 && (!!formattedMerchant || !!formattedDescription) && !(hasOnlyTransactionsWithPendingRoutes && !totalDisplaySpend);
const shouldShowSubtitle = !isScanning && (shouldShowSingleRequestMerchantOrDescription || numberOfRequests > 1);

return (
<OfflineWithFeedback
Expand Down
39 changes: 39 additions & 0 deletions src/components/withNavigationTransitionEnd.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {useNavigation} from '@react-navigation/native';
import type {StackNavigationProp} from '@react-navigation/stack';
import type {ComponentType, ForwardedRef, RefAttributes} from 'react';
import React, {useEffect, useState} from 'react';
import getComponentDisplayName from '@libs/getComponentDisplayName';
import type {RootStackParamList} from '@libs/Navigation/types';

type WithNavigationTransitionEndProps = {didScreenTransitionEnd: boolean};

export default function <TProps, TRef>(WrappedComponent: ComponentType<TProps & RefAttributes<TRef>>): React.ComponentType<TProps & RefAttributes<TRef>> {
function WithNavigationTransitionEnd(props: TProps, ref: ForwardedRef<TRef>) {
const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false);
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();

useEffect(() => {
const unsubscribeTransitionEnd = navigation.addListener('transitionEnd', () => {
setDidScreenTransitionEnd(true);
});

return unsubscribeTransitionEnd;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<WrappedComponent
// eslint-disable-next-line react/jsx-props-no-spreading
{...props}
didScreenTransitionEnd={didScreenTransitionEnd}
ref={ref}
/>
);
}

WithNavigationTransitionEnd.displayName = `WithNavigationTransitionEnd(${getComponentDisplayName(WrappedComponent)})`;

return React.forwardRef(WithNavigationTransitionEnd);
}

export type {WithNavigationTransitionEndProps};
2 changes: 1 addition & 1 deletion src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -518,7 +518,7 @@ export default {
asCopilot: 'as copilot for',
},
mentionSuggestions: {
hereAlternateText: 'Notify everyone online in this room',
hereAlternateText: 'Notify everyone in this conversation',
},
newMessages: 'New messages',
reportTypingIndicator: {
Expand Down
2 changes: 1 addition & 1 deletion src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -514,7 +514,7 @@ export default {
asCopilot: 'como copiloto de',
},
mentionSuggestions: {
hereAlternateText: 'Notificar a todos los que estén en linea de esta sala',
hereAlternateText: 'Notificar a todos en esta conversación',
},
newMessages: 'Mensajes nuevos',
reportTypingIndicator: {
Expand Down
12 changes: 11 additions & 1 deletion src/libs/PolicyUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import type {PersonalDetailsList, Policy, PolicyCategories, PolicyMembers, Polic
import type {PolicyFeatureName, Rate} from '@src/types/onyx/Policy';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import Navigation from './Navigation/Navigation';
import getPolicyIDFromState from './Navigation/getPolicyIDFromState';
import Navigation, {navigationRef} from './Navigation/Navigation';
import type {RootStackParamList, State} from './Navigation/types';

type MemberEmailsToAccountIDs = Record<string, number>;

Expand Down Expand Up @@ -304,6 +306,13 @@ function isPolicyFeatureEnabled(policy: OnyxEntry<Policy> | EmptyObject, feature
return Boolean(policy?.[featureName]);
}

/**
* Get the currently selected policy ID stored in the navigation state.
*/
function getPolicyIDFromNavigationState() {
return getPolicyIDFromState(navigationRef.getRootState() as State<RootStackParamList>);
}

export {
getActivePolicies,
hasAccountingConnections,
Expand Down Expand Up @@ -340,6 +349,7 @@ export {
hasTaxRateError,
getTaxByID,
hasPolicyCategoriesError,
getPolicyIDFromNavigationState,
};

export type {MemberEmailsToAccountIDs};
37 changes: 12 additions & 25 deletions src/pages/RoomInvitePage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import {useNavigation} from '@react-navigation/native';
import type {StackNavigationProp} from '@react-navigation/stack';
import Str from 'expensify-common/lib/str';
import React, {useCallback, useEffect, useMemo, useState} from 'react';
import type {SectionListData} from 'react-native';
Expand All @@ -13,12 +11,13 @@ import ScreenWrapper from '@components/ScreenWrapper';
import SelectionList from '@components/SelectionList';
import type {Section} from '@components/SelectionList/types';
import UserListItem from '@components/SelectionList/UserListItem';
import withNavigationTransitionEnd from '@components/withNavigationTransitionEnd';
import type {WithNavigationTransitionEndProps} from '@components/withNavigationTransitionEnd';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import * as LoginUtils from '@libs/LoginUtils';
import Navigation from '@libs/Navigation/Navigation';
import type {RootStackParamList} from '@libs/Navigation/types';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as PhoneNumber from '@libs/PhoneNumber';
Expand All @@ -39,19 +38,17 @@ type RoomInvitePageOnyxProps = {
personalDetails: OnyxEntry<PersonalDetailsList>;
};

type RoomInvitePageProps = RoomInvitePageOnyxProps & WithReportOrNotFoundProps;
type RoomInvitePageProps = RoomInvitePageOnyxProps & WithReportOrNotFoundProps & WithNavigationTransitionEndProps;

type Sections = Array<SectionListData<OptionsListUtils.MemberForList, Section<OptionsListUtils.MemberForList>>>;

function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePageProps) {
function RoomInvitePage({betas, personalDetails, report, policies, didScreenTransitionEnd}: RoomInvitePageProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const [searchTerm, setSearchTerm] = useState('');
const [selectedOptions, setSelectedOptions] = useState<ReportUtils.OptionData[]>([]);
const [invitePersonalDetails, setInvitePersonalDetails] = useState<ReportUtils.OptionData[]>([]);
const [userToInvite, setUserToInvite] = useState<ReportUtils.OptionData | null>(null);
const [didScreenTransitionEnd, setDidScreenTransitionEnd] = useState(false);
const navigation: StackNavigationProp<RootStackParamList> = useNavigation();

useEffect(() => {
setSearchTerm(SearchInputManager.searchInput);
Expand Down Expand Up @@ -88,18 +85,6 @@ function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePa
// eslint-disable-next-line react-hooks/exhaustive-deps -- we don't want to recalculate when selectedOptions change
}, [personalDetails, betas, searchTerm, excludedUsers]);

useEffect(() => {
const unsubscribeTransitionEnd = navigation.addListener('transitionEnd', () => {
setDidScreenTransitionEnd(true);
});

return () => {
unsubscribeTransitionEnd();
};
// Rule disabled because this effect is only for component did mount & will component unmount lifecycle event
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

const sections = useMemo(() => {
const sectionsArr: Sections = [];

Expand Down Expand Up @@ -260,10 +245,12 @@ function RoomInvitePage({betas, personalDetails, report, policies}: RoomInvitePa

RoomInvitePage.displayName = 'RoomInvitePage';

export default withReportOrNotFound()(
withOnyx<RoomInvitePageProps, RoomInvitePageOnyxProps>({
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
})(RoomInvitePage),
export default withNavigationTransitionEnd(
withReportOrNotFound()(
withOnyx<RoomInvitePageProps, RoomInvitePageOnyxProps>({
personalDetails: {
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
},
})(RoomInvitePage),
),
);
Loading

0 comments on commit 9f112ca

Please sign in to comment.