From 365aab9b1d0a971ce3601972deb38dd9d7ece8e0 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Mon, 23 Feb 2026 01:55:56 +0500 Subject: [PATCH 1/8] Add Sparkles icon to ExpensifyIcons --- assets/images/sparkles.svg | 5 +++++ src/components/Icon/chunks/expensify-icons.chunk.ts | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 assets/images/sparkles.svg diff --git a/assets/images/sparkles.svg b/assets/images/sparkles.svg new file mode 100644 index 0000000000000..e87e7c44abc00 --- /dev/null +++ b/assets/images/sparkles.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/components/Icon/chunks/expensify-icons.chunk.ts b/src/components/Icon/chunks/expensify-icons.chunk.ts index d5a621049af9d..37ef426e2d120 100644 --- a/src/components/Icon/chunks/expensify-icons.chunk.ts +++ b/src/components/Icon/chunks/expensify-icons.chunk.ts @@ -208,6 +208,7 @@ import Linkedin from '@assets/images/social-linkedin.svg'; import Podcast from '@assets/images/social-podcast.svg'; import Twitter from '@assets/images/social-twitter.svg'; import Youtube from '@assets/images/social-youtube.svg'; +import Sparkles from '@assets/images/sparkles.svg'; import SpreadsheetComputer from '@assets/images/spreadsheet-computer.svg'; import Star from '@assets/images/Star.svg'; import Stopwatch from '@assets/images/stopwatch.svg'; @@ -415,6 +416,7 @@ const Expensicons = { Stopwatch, Suitcase, Sync, + Sparkles, Task, ThumbsUp, ThreeDots, From 6590c3fc3d2f93cf2603e78884bd1478afe30518 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Mon, 23 Feb 2026 02:02:49 +0500 Subject: [PATCH 2/8] update copies in all locals --- src/languages/de.ts | 2 +- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/languages/fr.ts | 2 +- src/languages/it.ts | 2 +- src/languages/ja.ts | 2 +- src/languages/nl.ts | 2 +- src/languages/pl.ts | 2 +- src/languages/pt-BR.ts | 2 +- src/languages/zh-hans.ts | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/languages/de.ts b/src/languages/de.ts index 2a8b251db17b1..f6ed4fe1555d2 100644 --- a/src/languages/de.ts +++ b/src/languages/de.ts @@ -538,6 +538,7 @@ const translations: TranslationDeepObject = { quarter: 'Quartal', vacationDelegate: 'Urlaubsvertretung', expensifyLogo: 'Expensify-Logo', + explain: 'Erklären', }, socials: { podcast: 'Folgen Sie uns auf Podcast', @@ -1492,7 +1493,6 @@ const translations: TranslationDeepObject = { amountTooLargeError: 'Der Gesamtbetrag ist zu hoch. Verringere die Stunden oder reduziere den Satz.', }, correctRateError: 'Behebe den Kursfehler und versuche es erneut.', - AskToExplain: `. Erklären ✨`, policyRulesModifiedFields: { reimbursable: (value: boolean) => (value ? 'hat die Ausgabe als „erstattungsfähig“ markiert' : 'hat die Ausgabe als „nicht erstattungsfähig“ markiert'), billable: (value: boolean) => (value ? 'hat die Ausgabe als „verrechenbar“ markiert' : 'hat die Ausgabe als „nicht abrechenbar“ markiert'), diff --git a/src/languages/en.ts b/src/languages/en.ts index d5c0aeb138f6e..437c28ef0b855 100644 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -557,6 +557,7 @@ const translations = { quarter: 'Quarter', vacationDelegate: 'Vacation delegate', expensifyLogo: 'Expensify logo', + explain: 'Explain', }, socials: { podcast: 'Follow us on Podcast', @@ -1506,7 +1507,6 @@ const translations = { amountTooLargeError: 'The total amount is too large. Lower the hours or reduce the rate.', }, correctRateError: 'Fix the rate error and try again.', - AskToExplain: `. Explain ✨`, policyRulesModifiedFields: { reimbursable: (value: boolean) => (value ? 'marked the expense as "reimbursable"' : 'marked the expense as "non-reimbursable"'), billable: (value: boolean) => (value ? 'marked the expense as "billable"' : 'marked the expense as "non-billable"'), diff --git a/src/languages/es.ts b/src/languages/es.ts index dede4ccad19e0..610d78a01e1e5 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -405,6 +405,7 @@ const translations: TranslationDeepObject = { quarter: 'Trimestre', vacationDelegate: 'Delegado de vacaciones', expensifyLogo: 'Logo de Expensify', + explain: 'Explicar', }, socials: { podcast: 'Síguenos en Podcast', @@ -1343,7 +1344,6 @@ const translations: TranslationDeepObject = { amountTooLargeError: 'El importe total es demasiado alto. Reduce las horas o disminuye la tasa.', }, correctRateError: 'Corrige el error de la tasa y vuelve a intentarlo.', - AskToExplain: `. Explicar ✨`, policyRulesModifiedFields: { reimbursable: (value: boolean) => (value ? 'marcó el gasto como "reembolsable"' : 'marcó el gasto como "no reembolsable"'), billable: (value: boolean) => (value ? 'marcó el gasto como "facturable"' : 'marcó el gasto como "no facturable"'), diff --git a/src/languages/fr.ts b/src/languages/fr.ts index 624014061be15..9f14da36c16e0 100644 --- a/src/languages/fr.ts +++ b/src/languages/fr.ts @@ -538,6 +538,7 @@ const translations: TranslationDeepObject = { quarter: 'Trimestre', vacationDelegate: 'Délégué de vacances', expensifyLogo: 'Logo Expensify', + explain: 'Expliquer', }, socials: { podcast: 'Suivez-nous sur Podcast', @@ -1497,7 +1498,6 @@ const translations: TranslationDeepObject = { amountTooLargeError: 'Le montant total est trop élevé. Réduisez le nombre d’heures ou diminuez le taux.', }, correctRateError: 'Corrigez l’erreur de taux et réessayez.', - AskToExplain: `. Expliquer ✨`, policyRulesModifiedFields: { reimbursable: (value: boolean) => (value ? 'a marqué la dépense comme « remboursable »' : 'a marqué la dépense comme « non remboursable »'), billable: (value: boolean) => (value ? 'a marqué la dépense comme « facturable »' : 'a marqué la dépense comme « non refacturable »'), diff --git a/src/languages/it.ts b/src/languages/it.ts index 16f35a7e1ba35..e921ee13dfe98 100644 --- a/src/languages/it.ts +++ b/src/languages/it.ts @@ -538,6 +538,7 @@ const translations: TranslationDeepObject = { quarter: 'Trimestre', vacationDelegate: 'Delega ferie', expensifyLogo: 'Logo Expensify', + explain: 'Spiega', }, socials: { podcast: 'Seguici su Podcast', @@ -1489,7 +1490,6 @@ const translations: TranslationDeepObject = { amountTooLargeError: 'L’importo totale è troppo alto. Riduci le ore o abbassa la tariffa.', }, correctRateError: 'Correggi l’errore di tariffa e riprova.', - AskToExplain: `. Spiega ✨`, policyRulesModifiedFields: { reimbursable: (value: boolean) => (value ? 'ha contrassegnato la spesa come "rimborsabile"' : 'ha contrassegnato la spesa come "non rimborsabile"'), billable: (value: boolean) => (value ? 'ha contrassegnato la spesa come "fatturabile"' : 'ha contrassegnato la spesa come "non fatturabile"'), diff --git a/src/languages/ja.ts b/src/languages/ja.ts index aa873fba0bb70..e6b75c486ce51 100644 --- a/src/languages/ja.ts +++ b/src/languages/ja.ts @@ -537,6 +537,7 @@ const translations: TranslationDeepObject = { quarter: '四半期', vacationDelegate: '休暇代理人', expensifyLogo: 'Expensifyロゴ', + explain: '説明', }, socials: { podcast: 'ポッドキャストでフォロー', @@ -1482,7 +1483,6 @@ const translations: TranslationDeepObject = { amountTooLargeError: '合計金額が大きすぎます。時間を減らすか、レートを下げてください。', }, correctRateError: 'レートのエラーを修正して、もう一度お試しください。', - AskToExplain: `. 説明 ✨`, policyRulesModifiedFields: { reimbursable: (value: boolean) => (value ? '経費を「精算対象」としてマークしました' : '経費を「非精算」としてマークしました'), billable: (value: boolean) => (value ? '経費を「請求可能」に設定しました' : '経費を「請求不可」としてマークしました'), diff --git a/src/languages/nl.ts b/src/languages/nl.ts index d064fdd6e68d9..80190d190de98 100644 --- a/src/languages/nl.ts +++ b/src/languages/nl.ts @@ -537,6 +537,7 @@ const translations: TranslationDeepObject = { quarter: 'Kwartaal', vacationDelegate: 'Vertegenwoordiger tijdens vakantie', expensifyLogo: 'Expensify-logo', + explain: 'Uitleggen', }, socials: { podcast: 'Volg ons op Podcast', @@ -1487,7 +1488,6 @@ const translations: TranslationDeepObject = { amountTooLargeError: 'Het totale bedrag is te hoog. Verlaag het aantal uren of verlaag het tarief.', }, correctRateError: 'Herstel de koersfout en probeer het opnieuw.', - AskToExplain: `. Uitleggen ✨`, policyRulesModifiedFields: { reimbursable: (value: boolean) => (value ? 'heeft de uitgave gemarkeerd als „vergoedbaar”' : 'markeerde de uitgave als ‘niet-terugbetaalbaar’'), billable: (value: boolean) => (value ? 'heeft de uitgave gemarkeerd als ‘door te belasten’' : 'heeft de uitgave gemarkeerd als ‘niet factureerbaar’'), diff --git a/src/languages/pl.ts b/src/languages/pl.ts index 26711f3d65a54..881dd594b81b1 100644 --- a/src/languages/pl.ts +++ b/src/languages/pl.ts @@ -537,6 +537,7 @@ const translations: TranslationDeepObject = { quarter: 'Kwartał', vacationDelegate: 'Zastępca urlopowy', expensifyLogo: 'Logo Expensify', + explain: 'Wyjaśnij', }, socials: { podcast: 'Śledź nas na Podcast', @@ -1486,7 +1487,6 @@ const translations: TranslationDeepObject = { amountTooLargeError: 'Łączna kwota jest zbyt wysoka. Zmniejsz liczbę godzin lub obniż stawkę.', }, correctRateError: 'Napraw błąd stawki i spróbuj ponownie.', - AskToExplain: `. Wyjaśnij ✨`, policyRulesModifiedFields: { reimbursable: (value: boolean) => (value ? 'oznaczył(a) wydatek jako „podlegający zwrotowi”' : 'oznaczył(a) wydatek jako „niepodlegający zwrotowi”'), billable: (value: boolean) => (value ? 'oznaczył(-a) wydatek jako „fakturowany”' : 'oznaczono wydatek jako „niefakturowalny”'), diff --git a/src/languages/pt-BR.ts b/src/languages/pt-BR.ts index fa25aaeefb9e5..33abb5268f22f 100644 --- a/src/languages/pt-BR.ts +++ b/src/languages/pt-BR.ts @@ -536,6 +536,7 @@ const translations: TranslationDeepObject = { quarter: 'Trimestre', vacationDelegate: 'Delegado de férias', expensifyLogo: 'Logo da Expensify', + explain: 'Explicar', }, socials: { podcast: 'Siga-nos no Podcast', @@ -1484,7 +1485,6 @@ const translations: TranslationDeepObject = { amountTooLargeError: 'O valor total é muito alto. Diminua as horas ou reduza a tarifa.', }, correctRateError: 'Corrija o erro de taxa e tente novamente.', - AskToExplain: `. Explicar ✨`, policyRulesModifiedFields: { reimbursable: (value: boolean) => (value ? 'marcou a despesa como "reembolsável"' : 'marcou a despesa como “não reembolsável”'), billable: (value: boolean) => (value ? 'marcou a despesa como "faturável"' : 'marcou a despesa como “não faturável”'), diff --git a/src/languages/zh-hans.ts b/src/languages/zh-hans.ts index d9b83ec742c8e..9d92a85be4a71 100644 --- a/src/languages/zh-hans.ts +++ b/src/languages/zh-hans.ts @@ -533,6 +533,7 @@ const translations: TranslationDeepObject = { quarter: '季度', vacationDelegate: '休假代理', expensifyLogo: 'Expensify徽标', + explain: '说明', }, socials: { podcast: '在播客上关注我们', @@ -1458,7 +1459,6 @@ const translations: TranslationDeepObject = { amountTooLargeError: '总金额过大。请减少工时或降低费率。', }, correctRateError: '修复费率错误后重试。', - AskToExplain: `。说明 ✨`, policyRulesModifiedFields: { reimbursable: (value: boolean) => (value ? '已将该报销单标记为“可报销”' : '将该报销单标记为“不可报销”'), billable: (value: boolean) => (value ? '将该报销标记为“可计费”' : '将该报销标记为“不可计费”'), From 3beef1f893c665ef7a7b7b3d377dc9bcce25a527 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Mon, 23 Feb 2026 02:03:42 +0500 Subject: [PATCH 3/8] refactor ReportActionItemMessageWithExplain component --- .../ReportActionItemMessageWithExplain.tsx | 60 ++++++++++++------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx b/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx index ad6629117fcc2..ccee109058987 100644 --- a/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx +++ b/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx @@ -1,14 +1,18 @@ import React from 'react'; -import type {GestureResponderEvent} from 'react-native'; +import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; +import Icon from '@components/Icon'; import RenderHTML from '@components/RenderHTML'; +import TextBlock from '@components/TextBlock'; +import TextLinkBlock from '@components/TextLinkBlock'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; -import useEnvironment from '@hooks/useEnvironment'; +import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; -import {openLink} from '@libs/actions/Link'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import {explain} from '@libs/actions/Report'; import {hasReasoning} from '@libs/ReportActionsUtils'; -import CONST from '@src/CONST'; +import variables from '@styles/variables'; import type {Report, ReportAction} from '@src/types/onyx'; import ReportActionItemBasicMessage from './ReportActionItemBasicMessage'; @@ -31,31 +35,47 @@ type ReportActionItemMessageWithExplainProps = { * if the action has reasoning. */ function ReportActionItemMessageWithExplain({message, action, childReport, originalReport}: ReportActionItemMessageWithExplainProps) { + const theme = useTheme(); + const styles = useThemeStyles(); const {translate} = useLocalize(); const personalDetail = useCurrentUserPersonalDetails(); - const {environmentURL} = useEnvironment(); + const icons = useMemoizedLazyExpensifyIcons(['Sparkles']); const actionHasReasoning = hasReasoning(action); - const computedMessage = actionHasReasoning ? `${message}${translate('iou.AskToExplain')}` : message; - const handleLinkPress = (event: GestureResponderEvent | KeyboardEvent, href: string) => { - // Handle the special "Explain" link - if (href.endsWith(CONST.CONCIERGE_EXPLAIN_LINK_PATH)) { - explain(childReport, originalReport, action, translate, personalDetail.accountID, personalDetail?.timezone); - return; - } - - // For all other links, use the default link handler - openLink(href, environmentURL); + const handleExplainPress = () => { + explain(childReport, originalReport, action, translate, personalDetail.accountID, personalDetail?.timezone); }; + if (!actionHasReasoning) { + return ( + + ${message}`} /> + + ); + } + return ( - ${computedMessage}`} - isSelectable={false} - onLinkPress={handleLinkPress} - /> + + + + + + + ); } From a1ee238f9da52b00516221bffee85a80be009aad Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Mon, 23 Feb 2026 02:04:31 +0500 Subject: [PATCH 4/8] add mr0half in spacing --- src/styles/utils/spacing.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/styles/utils/spacing.ts b/src/styles/utils/spacing.ts index 743dd3e5be826..350e7be8b3ecb 100644 --- a/src/styles/utils/spacing.ts +++ b/src/styles/utils/spacing.ts @@ -143,6 +143,10 @@ export default { marginRight: 0, }, + mr0half: { + marginRight: 2, + }, + mr1: { marginRight: 4, }, From 29d092bb1d5d602b9344996a86d6784ed1085723 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Mon, 23 Feb 2026 03:33:39 +0500 Subject: [PATCH 5/8] fix failing checks --- assets/images/sparkles.svg | 6 +----- .../report/ReportActionItemMessageWithExplain.tsx | 10 +++++++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/assets/images/sparkles.svg b/assets/images/sparkles.svg index e87e7c44abc00..315e9b51350b2 100644 --- a/assets/images/sparkles.svg +++ b/assets/images/sparkles.svg @@ -1,5 +1 @@ - - - - - + \ No newline at end of file diff --git a/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx b/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx index ccee109058987..e863402600674 100644 --- a/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx +++ b/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx @@ -6,10 +6,12 @@ import RenderHTML from '@components/RenderHTML'; import TextBlock from '@components/TextBlock'; import TextLinkBlock from '@components/TextLinkBlock'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; +import useEnvironment from '@hooks/useEnvironment'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import {openLink} from '@libs/actions/Link'; import {explain} from '@libs/actions/Report'; import {hasReasoning} from '@libs/ReportActionsUtils'; import variables from '@styles/variables'; @@ -40,6 +42,7 @@ function ReportActionItemMessageWithExplain({message, action, childReport, origi const {translate} = useLocalize(); const personalDetail = useCurrentUserPersonalDetails(); const icons = useMemoizedLazyExpensifyIcons(['Sparkles']); + const {environmentURL} = useEnvironment(); const actionHasReasoning = hasReasoning(action); @@ -50,7 +53,12 @@ function ReportActionItemMessageWithExplain({message, action, childReport, origi if (!actionHasReasoning) { return ( - ${message}`} /> + ${message}`} + onLinkPress={(event, href) => { + openLink(href, environmentURL); + }} + /> ); } From 9c4a325716b8b53206022f71df8ce26761b0a6b6 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Wed, 25 Feb 2026 02:53:47 +0500 Subject: [PATCH 6/8] remove Sparkles icon --- assets/images/sparkles.svg | 1 - src/components/Icon/chunks/expensify-icons.chunk.ts | 2 -- 2 files changed, 3 deletions(-) delete mode 100644 assets/images/sparkles.svg diff --git a/assets/images/sparkles.svg b/assets/images/sparkles.svg deleted file mode 100644 index 315e9b51350b2..0000000000000 --- a/assets/images/sparkles.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/components/Icon/chunks/expensify-icons.chunk.ts b/src/components/Icon/chunks/expensify-icons.chunk.ts index 37ef426e2d120..d5a621049af9d 100644 --- a/src/components/Icon/chunks/expensify-icons.chunk.ts +++ b/src/components/Icon/chunks/expensify-icons.chunk.ts @@ -208,7 +208,6 @@ import Linkedin from '@assets/images/social-linkedin.svg'; import Podcast from '@assets/images/social-podcast.svg'; import Twitter from '@assets/images/social-twitter.svg'; import Youtube from '@assets/images/social-youtube.svg'; -import Sparkles from '@assets/images/sparkles.svg'; import SpreadsheetComputer from '@assets/images/spreadsheet-computer.svg'; import Star from '@assets/images/Star.svg'; import Stopwatch from '@assets/images/stopwatch.svg'; @@ -416,7 +415,6 @@ const Expensicons = { Stopwatch, Suitcase, Sync, - Sparkles, Task, ThumbsUp, ThreeDots, From 99f9c6f9bc713b689621cfd7051058c50ef96096 Mon Sep 17 00:00:00 2001 From: Samran Ahmed Date: Thu, 26 Feb 2026 20:13:31 +0500 Subject: [PATCH 7/8] apply review suggestions --- .../inbox/report/ReportActionItemMessageWithExplain.tsx | 8 ++------ src/styles/utils/spacing.ts | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx b/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx index e863402600674..94bb68316254c 100644 --- a/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx +++ b/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx @@ -46,10 +46,6 @@ function ReportActionItemMessageWithExplain({message, action, childReport, origi const actionHasReasoning = hasReasoning(action); - const handleExplainPress = () => { - explain(childReport, originalReport, action, translate, personalDetail.accountID, personalDetail?.timezone); - }; - if (!actionHasReasoning) { return ( @@ -72,8 +68,8 @@ function ReportActionItemMessageWithExplain({message, action, childReport, origi /> explain(childReport, originalReport, action, translate, personalDetail.accountID, personalDetail?.timezone)} + style={[styles.chatItemMessage, styles.link, styles.mrhalf]} text={translate('common.explain')} /> Date: Thu, 26 Feb 2026 20:24:49 +0500 Subject: [PATCH 8/8] fiix spell check --- src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx | 2 +- src/styles/utils/spacing.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx b/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx index 94bb68316254c..67325f1f2b840 100644 --- a/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx +++ b/src/pages/inbox/report/ReportActionItemMessageWithExplain.tsx @@ -69,7 +69,7 @@ function ReportActionItemMessageWithExplain({message, action, childReport, origi explain(childReport, originalReport, action, translate, personalDetail.accountID, personalDetail?.timezone)} - style={[styles.chatItemMessage, styles.link, styles.mrhalf]} + style={[styles.chatItemMessage, styles.link, styles.mrHalf]} text={translate('common.explain')} />