Skip to content

Commit 07d285c

Browse files
committed
Merge branch 'main' into merge-expenses-phase-1
2 parents 666ba84 + 557e520 commit 07d285c

24 files changed

Lines changed: 164 additions & 197 deletions

package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@
179179
"react-native-modal": "^13.0.0",
180180
"react-native-nitro-modules": "0.26.2",
181181
"react-native-nitro-sqlite": "9.1.10",
182-
"react-native-onyx": "2.0.130",
182+
"react-native-onyx": "2.0.127",
183183
"react-native-pager-view": "6.5.3",
184184
"react-native-pdf": "6.7.3",
185185
"react-native-performance": "^5.1.4",

src/components/AddPaymentCard/PaymentCardCurrencyHeader.tsx

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,15 @@
11
import React from 'react';
22
import {View} from 'react-native';
3-
import Text from '@components/Text';
4-
import TextLink from '@components/TextLink';
3+
import RenderHTML from '@components/RenderHTML';
54
import useLocalize from '@hooks/useLocalize';
65
import useThemeStyles from '@hooks/useThemeStyles';
7-
import CONST from '@src/CONST';
86

97
function PaymentCardCurrencyHeader({isSectionList}: {isSectionList?: boolean}) {
108
const styles = useThemeStyles();
119
const {translate} = useLocalize();
1210
return (
13-
<View style={[isSectionList && styles.mh5]}>
14-
<Text style={[styles.mt3, isSectionList && styles.mb5]}>
15-
{`${translate('billingCurrency.note')}`}{' '}
16-
<TextLink
17-
style={styles.link}
18-
href={CONST.PRICING}
19-
>{`${translate('billingCurrency.noteLink')}`}</TextLink>{' '}
20-
{`${translate('billingCurrency.noteDetails')}`}
21-
</Text>
11+
<View style={[styles.renderHTML, styles.flexRow, styles.mt3, isSectionList && styles.mh5, isSectionList && styles.mb5]}>
12+
<RenderHTML html={translate('billingCurrency.note')} />
2213
</View>
2314
);
2415
}

src/components/DelegateNoAccessModalProvider.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import React, {createContext, useMemo, useState} from 'react';
22
import type {PropsWithChildren} from 'react';
3+
import {View} from 'react-native';
34
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
45
import useLocalize from '@hooks/useLocalize';
56
import useOnyx from '@hooks/useOnyx';
7+
import useThemeStyles from '@hooks/useThemeStyles';
68
import AccountUtils from '@libs/AccountUtils';
7-
import CONST from '@src/CONST';
89
import ONYXKEYS from '@src/ONYXKEYS';
910
import ConfirmModal from './ConfirmModal';
10-
import Text from './Text';
11-
import TextLink from './TextLink';
11+
import RenderHTML from './RenderHTML';
1212

1313
type DelegateNoAccessContextType = {
1414
/** Whether the current user is acting as delegate */
@@ -29,6 +29,7 @@ const DelegateNoAccessContext = createContext<DelegateNoAccessContextType>({
2929

3030
function DelegateNoAccessModalProvider({children}: PropsWithChildren) {
3131
const {translate} = useLocalize();
32+
const styles = useThemeStyles();
3233
const [isModalOpen, setIsModalOpen] = useState(false);
3334
const currentUserDetails = useCurrentUserPersonalDetails();
3435
const delegatorEmail = currentUserDetails?.login ?? '';
@@ -37,11 +38,9 @@ function DelegateNoAccessModalProvider({children}: PropsWithChildren) {
3738
const isDelegateAccessRestricted = isActingAsDelegate && AccountUtils.isDelegateOnlySubmitter(account);
3839

3940
const delegateNoAccessPrompt = (
40-
<Text>
41-
{translate('delegate.notAllowedMessageStart')}
42-
<TextLink href={CONST.DELEGATE_ROLE_HELP_DOT_ARTICLE_LINK}>{translate('delegate.notAllowedMessageHyperLinked')}</TextLink>
43-
{translate('delegate.notAllowedMessageEnd', {accountOwnerEmail: delegatorEmail})}
44-
</Text>
41+
<View style={[styles.renderHTML, styles.flexRow]}>
42+
<RenderHTML html={translate('delegate.notAllowedMessage', {accountOwnerEmail: delegatorEmail})} />
43+
</View>
4544
);
4645
const contextValue = useMemo(
4746
() => ({

src/components/HTMLEngineProvider/BaseHTMLEngineProvider.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ function BaseHTMLEngineProvider({textSelectable = false, children, enableExperim
130130
'mention-report': HTMLElementModel.fromCustomModel({tagName: 'mention-report', contentModel: HTMLContentModel.textual}),
131131
'mention-here': HTMLElementModel.fromCustomModel({tagName: 'mention-here', contentModel: HTMLContentModel.textual}),
132132
'mention-short': HTMLElementModel.fromCustomModel({tagName: 'mention-short', contentModel: HTMLContentModel.textual}),
133+
'concierge-link': HTMLElementModel.fromCustomModel({tagName: 'concierge-link', contentModel: HTMLContentModel.textual}),
133134
'next-step': HTMLElementModel.fromCustomModel({
134135
tagName: 'next-step',
135136
mixedUAStyles: {...styles.textLabelSupporting, ...styles.lh16},

src/components/HTMLEngineProvider/HTMLRenderers/AnchorRenderer.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {Str} from 'expensify-common';
22
import React, {useMemo} from 'react';
33
import type {StyleProp, TextStyle} from 'react-native';
4-
import type {CustomRendererProps, TBlock} from 'react-native-render-html';
4+
import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html';
55
import {TNodeChildrenRenderer} from 'react-native-render-html';
66
import AnchorForAttachmentsOnly from '@components/AnchorForAttachmentsOnly';
77
import AnchorForCommentsOnly from '@components/AnchorForCommentsOnly';
@@ -16,7 +16,7 @@ import {getInternalExpensifyPath, getInternalNewExpensifyPath, openLink} from '@
1616
import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot';
1717
import CONST from '@src/CONST';
1818

19-
type AnchorRendererProps = CustomRendererProps<TBlock> & {
19+
type AnchorRendererProps = CustomRendererProps<TText | TPhrasing> & {
2020
/** Key of the element */
2121
key?: string;
2222
};
@@ -71,6 +71,11 @@ function AnchorRenderer({tnode, style, key}: AnchorRendererProps) {
7171
];
7272
}
7373

74+
// Special handling for links in RBR to maintain consistent font size
75+
if (HTMLEngineUtils.isChildOfMutedTextLabel(tnode)) {
76+
linkStyle = [styles.mutedNormalTextLabel, styles.link];
77+
}
78+
7479
if (tnode.classes.includes('no-style-link')) {
7580
// If the link has a class of a no-style-link, we don't apply any styles
7681
linkStyle = {...(style as TextStyle)};
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import React from 'react';
2+
import type {StyleProp, TextStyle} from 'react-native';
3+
import type {CustomRendererProps, TPhrasing, TText} from 'react-native-render-html';
4+
import {TNodeChildrenRenderer} from 'react-native-render-html';
5+
import * as HTMLEngineUtils from '@components/HTMLEngineProvider/htmlEngineUtils';
6+
import Text from '@components/Text';
7+
import useThemeStyles from '@hooks/useThemeStyles';
8+
import {navigateToConciergeChat as navigateToConciergeChatAction} from '@userActions/Report';
9+
10+
type ConciergeLinkRendererProps = CustomRendererProps<TText | TPhrasing>;
11+
12+
/**
13+
* Simple wrapper to create a stable reference without passing event args to navigation function.
14+
*/
15+
function navigateToConciergeChat() {
16+
navigateToConciergeChatAction();
17+
}
18+
19+
function ConciergeLinkRenderer({tnode}: ConciergeLinkRendererProps) {
20+
const styles = useThemeStyles();
21+
22+
// Define link style based on context
23+
let linkStyle: StyleProp<TextStyle> = styles.link;
24+
25+
// Special handling for links in RBR to maintain consistent font size
26+
if (HTMLEngineUtils.isChildOfRBR(tnode)) {
27+
linkStyle = [
28+
styles.link,
29+
{
30+
fontSize: HTMLEngineUtils.getFontSizeOfRBRChild(tnode),
31+
},
32+
];
33+
}
34+
35+
return (
36+
<Text
37+
style={linkStyle}
38+
onPress={navigateToConciergeChat}
39+
suppressHighlighting
40+
>
41+
<TNodeChildrenRenderer tnode={tnode} />
42+
</Text>
43+
);
44+
}
45+
46+
ConciergeLinkRenderer.displayName = 'ConciergeLinkRenderer';
47+
48+
export default ConciergeLinkRenderer;

src/components/HTMLEngineProvider/HTMLRenderers/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {CustomTagRendererRecord} from 'react-native-render-html';
22
import AnchorRenderer from './AnchorRenderer';
33
import CodeRenderer from './CodeRenderer';
4+
import ConciergeLinkRenderer from './ConciergeLinkRenderer';
45
import DeletedActionRenderer from './DeletedActionRenderer';
56
import EditedRenderer from './EditedRenderer';
67
import EmojiRenderer from './EmojiRenderer';
@@ -38,6 +39,7 @@ const HTMLEngineProviderComponentList: CustomTagRendererRecord = {
3839
emoji: EmojiRenderer,
3940
'next-step-email': NextStepEmailRenderer,
4041
'deleted-action': DeletedActionRenderer,
42+
'concierge-link': ConciergeLinkRenderer,
4143
/* eslint-enable @typescript-eslint/naming-convention */
4244
};
4345

src/components/HTMLEngineProvider/htmlEngineUtils.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,17 @@ function getFontSizeOfRBRChild(tnode: TNode): number {
9898
return 0;
9999
}
100100

101-
export {computeEmbeddedMaxWidth, isChildOfComment, isChildOfH1, isDeletedNode, isChildOfTaskTitle, isChildOfRBR, isCommentTag, getFontSizeOfRBRChild};
101+
/**
102+
* @returns Whether the node is a child of muted-text-label
103+
*/
104+
function isChildOfMutedTextLabel(tnode: TNode): boolean {
105+
if (!tnode.parent) {
106+
return false;
107+
}
108+
if (tnode.parent.tagName === 'muted-text-label') {
109+
return true;
110+
}
111+
return isChildOfMutedTextLabel(tnode.parent);
112+
}
113+
114+
export {computeEmbeddedMaxWidth, isChildOfComment, isChildOfH1, isDeletedNode, isChildOfTaskTitle, isChildOfRBR, isCommentTag, getFontSizeOfRBRChild, isChildOfMutedTextLabel};

src/hooks/useSidebarOrderedReports.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import {deepEqual} from 'fast-equals';
22
import React, {createContext, useCallback, useContext, useEffect, useMemo, useRef, useState} from 'react';
3-
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
3+
import type {OnyxEntry} from 'react-native-onyx';
44
import Log from '@libs/Log';
55
import {getPolicyEmployeeListByIdWithoutCurrentUser} from '@libs/PolicyUtils';
6-
import {reportAttributesSelector} from '@libs/ReportUtils';
76
import SidebarUtils from '@libs/SidebarUtils';
87
import CONST from '@src/CONST';
98
import ONYXKEYS from '@src/ONYXKEYS';
@@ -45,8 +44,6 @@ const policySelector = (policy: OnyxEntry<OnyxTypes.Policy>): PartialPolicyForSi
4544
employeeList: policy.employeeList,
4645
}) as PartialPolicyForSidebar;
4746

48-
const policiesSelector = (policies: OnyxCollection<OnyxTypes.Policy>) => mapOnyxCollectionItems(policies, policySelector);
49-
5047
function SidebarOrderedReportsContextProvider({
5148
children,
5249
/**
@@ -62,13 +59,13 @@ function SidebarOrderedReportsContextProvider({
6259
const {localeCompare} = useLocalize();
6360
const [priorityMode = CONST.PRIORITY_MODE.DEFAULT] = useOnyx(ONYXKEYS.NVP_PRIORITY_MODE, {canBeMissing: true});
6461
const [chatReports, {sourceValue: reportUpdates}] = useOnyx(ONYXKEYS.COLLECTION.REPORT, {canBeMissing: true});
65-
const [policies, {sourceValue: policiesUpdates}] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: policiesSelector, canBeMissing: true});
62+
const [policies, {sourceValue: policiesUpdates}] = useOnyx(ONYXKEYS.COLLECTION.POLICY, {selector: (c) => mapOnyxCollectionItems(c, policySelector), canBeMissing: true});
6663
const [transactions, {sourceValue: transactionsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {canBeMissing: true});
6764
const [transactionViolations, {sourceValue: transactionViolationsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true});
6865
const [reportNameValuePairs, {sourceValue: reportNameValuePairsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {canBeMissing: true});
6966
const [, {sourceValue: reportsDraftsUpdates}] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, {canBeMissing: true});
7067
const [betas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: true});
71-
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: reportAttributesSelector, canBeMissing: true});
68+
const [reportAttributes] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {selector: (value) => value?.reports, canBeMissing: true});
7269
const [currentReportsToDisplay, setCurrentReportsToDisplay] = useState<ReportsToDisplayInLHN>({});
7370

7471
const {shouldUseNarrowLayout} = useResponsiveLayout();

0 commit comments

Comments
 (0)