Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
28e2210
fix: use PressableWithoutFocus on enlarge button to prevent blue focu…
TaduJR Mar 6, 2026
48438e9
fix: validate attachment files and restrict file types on receipt add…
TaduJR Mar 6, 2026
54d5e43
fix: notify transaction thread report for scroll-to-bottom after addi…
TaduJR Mar 6, 2026
98e7b98
fix: show receipt action buttons when hovering during image load
TaduJR Mar 6, 2026
0696ce7
Merge branch 'main' of https://github.com/TaduJR/App into feat-Allow-…
TaduJR Mar 7, 2026
9f016dd
fix: use hasHoverSupport instead of canUseTouchScreen for receipt act…
TaduJR Mar 7, 2026
1125ca7
Merge branch 'main' of https://github.com/TaduJR/App into feat-Allow-…
TaduJR Mar 9, 2026
465391b
feat: add hover states to receipt action buttons
TaduJR Mar 9, 2026
1ee46b4
Merge branch 'main' of https://github.com/TaduJR/App into feat-Allow-…
TaduJR Mar 12, 2026
7463094
feat: add tooltips to receipt action buttons
TaduJR Mar 12, 2026
a928724
Merge branch 'main' of https://github.com/TaduJR/App into feat-Allow-…
TaduJR Mar 15, 2026
56c8916
Merge branch 'main' of https://github.com/TaduJR/App into feat-Allow-…
TaduJR Mar 15, 2026
8a81fca
fix: reset stale hover states after file picker cancel and improve fi…
TaduJR Mar 16, 2026
41c1ce6
Merge branch 'main' of https://github.com/TaduJR/App into feat-Allow-…
TaduJR Mar 16, 2026
0b77c55
fix: notify both transaction thread and money request report for scro…
TaduJR Mar 16, 2026
777b863
test: update receipt button accessibility label in tests
TaduJR Mar 16, 2026
031b56c
fix: keep receipt action buttons visible when offline
TaduJR Mar 16, 2026
e0863bb
Merge branch 'main' of https://github.com/TaduJR/App into feat-Allow-…
TaduJR Mar 16, 2026
017a0d6
fix: reset stale hover states after file picker cancel and improve fi…
TaduJR Mar 16, 2026
663da44
Merge branch 'main' of https://github.com/TaduJR/App into feat-Allow-…
TaduJR Mar 17, 2026
870f9e1
fix: add early return for undefined reportID in notifyNewAction
TaduJR Mar 17, 2026
c20fc25
refactor: extract receipt hover utilities into platform-specific files
TaduJR Mar 17, 2026
03f0241
fix: center loading indicator in receipt image container
TaduJR Mar 17, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
172 changes: 109 additions & 63 deletions src/components/ReportActionItem/MoneyRequestReceiptView.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// No-op on native — hover states don't exist on mobile
function resetButtonHoverState() {}

function isElementHovered(): boolean {
return false;
}

export {resetButtonHoverState, isElementHovered};
16 changes: 16 additions & 0 deletions src/components/ReportActionItem/receiptHoverUtils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type {RefObject} from 'react';
import type {View} from 'react-native';

/** Reset stale button hover/tooltip when file picker opens (browsers don't fire mouseleave). */
function resetButtonHoverState(addButtonRef: RefObject<View | null>) {
const buttonEl = addButtonRef.current as unknown as HTMLElement;
buttonEl?.dispatchEvent(new PointerEvent('pointerleave'));
buttonEl?.dispatchEvent(new MouseEvent('mouseout', {bubbles: true, relatedTarget: document.body}));
}

/** Check if cursor is over the element (web only). */
function isElementHovered(ref: RefObject<View | null>): boolean {
return !!(ref.current as unknown as HTMLElement)?.matches?.(':hover');
}

export {resetButtonHoverState, isElementHovered};
8 changes: 4 additions & 4 deletions src/hooks/useHover.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {useState} from 'react';
import {canUseTouchScreen as canUseTouchScreenUtil} from '@libs/DeviceCapabilities';
import {hasHoverSupport} from '@libs/DeviceCapabilities';

const useHover = () => {
const [hovered, setHovered] = useState(false);
const canUseTouchScreen = canUseTouchScreenUtil();
const deviceHasHoverSupport = hasHoverSupport();
return {
hovered,
bind: {
onMouseEnter: () => !canUseTouchScreen && setHovered(true),
onMouseLeave: () => !canUseTouchScreen && setHovered(false),
onMouseEnter: () => deviceHasHoverSupport && setHovered(true),
onMouseLeave: () => deviceHasHoverSupport && setHovered(false),
},
};
};
Expand Down
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,7 @@ const translations: TranslationDeepObject<typeof en> = {
deleteReceipt: 'Beleg löschen',
deleteConfirmation: 'Sind Sie sicher, dass Sie diesen Beleg löschen möchten?',
addReceipt: 'Beleg hinzufügen',
addAdditionalReceipt: 'Zusätzlichen Beleg hinzufügen',
scanFailed: 'Der Beleg konnte nicht gescannt werden, da Händler, Datum oder Betrag fehlen.',
crop: 'Zuschneiden',
addAReceipt: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1182,6 +1182,7 @@ const translations = {
deleteReceipt: 'Delete receipt',
deleteConfirmation: 'Are you sure you want to delete this receipt?',
addReceipt: 'Add receipt',
addAdditionalReceipt: 'Add additional receipt',
scanFailed: "The receipt couldn't be scanned, as it's missing a merchant, date, or amount.",
crop: 'Crop',
addAReceipt: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,7 @@ const translations: TranslationDeepObject<typeof en> = {
deleteReceipt: 'Eliminar recibo',
deleteConfirmation: '¿Estás seguro de que quieres borrar este recibo?',
addReceipt: 'Añadir recibo',
addAdditionalReceipt: 'Añadir recibo adicional',
scanFailed: 'El recibo no pudo ser escaneado, ya que falta el comerciante, la fecha o el monto.',
crop: 'Recortar',
addAReceipt: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,7 @@ const translations: TranslationDeepObject<typeof en> = {
deleteReceipt: 'Supprimer le reçu',
deleteConfirmation: 'Voulez-vous vraiment supprimer ce reçu ?',
addReceipt: 'Ajouter un reçu',
addAdditionalReceipt: 'Ajouter un reçu supplémentaire',
scanFailed: 'Le reçu n’a pas pu être scanné, car il lui manque un commerçant, une date ou un montant.',
crop: 'Recadrer',
addAReceipt: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,7 @@ const translations: TranslationDeepObject<typeof en> = {
deleteReceipt: 'Elimina ricevuta',
deleteConfirmation: 'Sei sicuro di voler eliminare questa ricevuta?',
addReceipt: 'Aggiungi ricevuta',
addAdditionalReceipt: 'Aggiungi ricevuta aggiuntiva',
scanFailed: 'La ricevuta non può essere acquisita perché manca il nome dell’esercente, la data o l’importo.',
crop: 'Ritaglia',
addAReceipt: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,7 @@ const translations: TranslationDeepObject<typeof en> = {
deleteReceipt: '領収書を削除',
deleteConfirmation: 'この領収書を削除してもよろしいですか?',
addReceipt: '領収書を追加',
addAdditionalReceipt: 'レシートを追加',
scanFailed: 'このレシートは、店舗名、日付、または金額が不足しているためスキャンできませんでした。',
crop: 'トリミング',
addAReceipt: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,7 @@ const translations: TranslationDeepObject<typeof en> = {
deleteReceipt: 'Bon verwijderen',
deleteConfirmation: 'Weet je zeker dat je deze bon wilt verwijderen?',
addReceipt: 'Bon toevoegen',
addAdditionalReceipt: 'Voeg extra bon toe',
scanFailed: 'De bon is niet gescand, omdat er een handelaar, datum of bedrag ontbreekt.',
crop: 'Bijsnijden',
addAReceipt: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1138,6 +1138,7 @@ const translations: TranslationDeepObject<typeof en> = {
deleteReceipt: 'Usuń paragon',
deleteConfirmation: 'Czy na pewno chcesz usunąć ten paragon?',
addReceipt: 'Dodaj paragon',
addAdditionalReceipt: 'Dodaj dodatkowy paragon',
scanFailed: 'Nie można było zeskanować paragonu, ponieważ brakuje na nim sprzedawcy, daty lub kwoty.',
crop: 'Przytnij',
addAReceipt: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1137,6 +1137,7 @@ const translations: TranslationDeepObject<typeof en> = {
deleteReceipt: 'Excluir recibo',
deleteConfirmation: 'Tem certeza de que deseja excluir este recibo?',
addReceipt: 'Adicionar recibo',
addAdditionalReceipt: 'Adicionar recibo adicional',
scanFailed: 'O recibo não pôde ser digitalizado porque está faltando o comerciante, a data ou o valor.',
crop: 'Cortar',
addAReceipt: {
Expand Down
1 change: 1 addition & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,7 @@ const translations: TranslationDeepObject<typeof en> = {
deleteReceipt: '删除收据',
deleteConfirmation: '确定要删除这张收据吗?',
addReceipt: '添加收据',
addAdditionalReceipt: '添加额外收据',
scanFailed: '无法扫描此收据,因为缺少商家、日期或金额。',
crop: '裁剪',
addAReceipt: {
Expand Down
17 changes: 11 additions & 6 deletions src/libs/actions/Report/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@

type AddActionsParams = {
report: OnyxEntry<Report>;
notifyReportID: string;
notifyReportID: string | string[];
ancestors: Ancestor[];
timezoneParam: Timezone;
currentUserAccountID: number;
Expand All @@ -359,7 +359,7 @@

type AddAttachmentWithCommentParams = {
report: OnyxEntry<Report>;
notifyReportID: string;
notifyReportID: string | string[];
ancestors: Ancestor[];
attachments: FileObject | FileObject[];
currentUserAccountID: number;
Expand All @@ -373,7 +373,7 @@
// map of reportID to all reportActions for that report
const allReportActions: OnyxCollection<ReportActions> = {};

Onyx.connect({

Check warning on line 376 in src/libs/actions/Report/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
callback: (actions, key) => {
if (!key || !actions) {
Expand All @@ -385,7 +385,7 @@
});

let allReports: OnyxCollection<Report>;
Onyx.connect({

Check warning on line 388 in src/libs/actions/Report/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.COLLECTION.REPORT,
waitForCollectionCallback: true,
callback: (value) => {
Expand All @@ -394,7 +394,7 @@
});

let allPersonalDetails: OnyxEntry<PersonalDetailsList> = {};
Onyx.connect({

Check warning on line 397 in src/libs/actions/Report/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.PERSONAL_DETAILS_LIST,
callback: (value) => {
allPersonalDetails = value ?? {};
Expand All @@ -412,7 +412,7 @@
});

let onboarding: OnyxEntry<Onboarding>;
Onyx.connect({

Check warning on line 415 in src/libs/actions/Report/index.ts

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Onyx.connect() is deprecated. Use useOnyx() hook instead and pass the data as parameters to a pure function
key: ONYXKEYS.NVP_ONBOARDING,
callback: (val) => {
if (Array.isArray(val)) {
Expand Down Expand Up @@ -643,12 +643,17 @@
}

/** Notify the ReportActionsView that a new comment has arrived */
function notifyNewAction(reportID: string | undefined, reportAction: ReportAction | undefined, isFromCurrentUser: boolean) {
const actionSubscriber = newActionSubscribers.find((subscriber) => subscriber.reportID === reportID);
if (!actionSubscriber) {
function notifyNewAction(reportID: string | string[] | undefined, reportAction: ReportAction | undefined, isFromCurrentUser: boolean) {
if (!reportID) {
return;
}
actionSubscriber.callback(isFromCurrentUser, reportAction);
const ids = Array.isArray(reportID) ? reportID : [reportID];
for (const id of ids) {
const actionSubscriber = newActionSubscribers.find((subscriber) => subscriber.reportID === id);
if (actionSubscriber) {
actionSubscriber.callback(isFromCurrentUser, reportAction);
}
}
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/styles/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3627,6 +3627,8 @@ const staticStyles = (theme: ThemeColors) =>
height: 40,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: theme.buttonDefaultBG,
borderRadius: 20,
},

bgGreenSuccess: {
Expand Down
8 changes: 4 additions & 4 deletions tests/ui/components/MoneyRequestReceiptViewTest.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ describe('MoneyRequestReceiptView', () => {
await waitForBatchedUpdatesWithAct();

expect(screen.queryByLabelText(translateLocal('accessibilityHints.viewAttachment'))).toBeNull();
expect(screen.queryByLabelText(translateLocal('reportActionCompose.addAttachment'))).toBeNull();
expect(screen.queryByLabelText(translateLocal('receipt.addAdditionalReceipt'))).toBeNull();
});

it('shows action buttons when transaction has a receipt', async () => {
Expand All @@ -264,7 +264,7 @@ describe('MoneyRequestReceiptView', () => {
await waitForBatchedUpdatesWithAct();

expect(screen.getByLabelText(translateLocal('accessibilityHints.viewAttachment'))).toBeTruthy();
expect(screen.getByLabelText(translateLocal('reportActionCompose.addAttachment'))).toBeTruthy();
expect(screen.getByLabelText(translateLocal('receipt.addAdditionalReceipt'))).toBeTruthy();
});

it('shows action buttons when receipt is scanning', async () => {
Expand All @@ -281,7 +281,7 @@ describe('MoneyRequestReceiptView', () => {
await waitForBatchedUpdatesWithAct();

expect(screen.getByLabelText(translateLocal('accessibilityHints.viewAttachment'))).toBeTruthy();
expect(screen.getByLabelText(translateLocal('reportActionCompose.addAttachment'))).toBeTruthy();
expect(screen.getByLabelText(translateLocal('receipt.addAdditionalReceipt'))).toBeTruthy();
});

it('does not show action buttons in readonly mode', async () => {
Expand All @@ -301,7 +301,7 @@ describe('MoneyRequestReceiptView', () => {
await waitForBatchedUpdatesWithAct();

expect(screen.queryByLabelText(translateLocal('accessibilityHints.viewAttachment'))).toBeNull();
expect(screen.queryByLabelText(translateLocal('reportActionCompose.addAttachment'))).toBeNull();
expect(screen.queryByLabelText(translateLocal('receipt.addAdditionalReceipt'))).toBeNull();
});
});
});
Loading