diff --git a/packages/desktop-client/e2e/page-models/mobile-payees-page.ts b/packages/desktop-client/e2e/page-models/mobile-payees-page.ts
index 8e7bf30a2f..6f06bdb5c3 100644
--- a/packages/desktop-client/e2e/page-models/mobile-payees-page.ts
+++ b/packages/desktop-client/e2e/page-models/mobile-payees-page.ts
@@ -51,7 +51,7 @@ export class MobilePayeesPage {
}
/**
- * Click on a payee to view/edit rules
+ * Click on a payee to open the edit page
*/
async clickPayee(index: number) {
const payee = this.getNthPayee(index);
diff --git a/packages/desktop-client/e2e/payees.mobile.test.ts b/packages/desktop-client/e2e/payees.mobile.test.ts
index 07d4f45245..4c22818d55 100644
--- a/packages/desktop-client/e2e/payees.mobile.test.ts
+++ b/packages/desktop-client/e2e/payees.mobile.test.ts
@@ -62,7 +62,7 @@ test.describe('Mobile Payees', () => {
await expect(page).toMatchThemeScreenshots();
});
- test('clicking on a payee opens rule creation form', async () => {
+ test('clicking on a payee opens payee edit page', async () => {
await payeesPage.waitForLoadingToComplete();
const payeeCount = await payeesPage.getPayeeCount();
@@ -70,8 +70,16 @@ test.describe('Mobile Payees', () => {
await payeesPage.clickPayee(0);
- // Should navigate to rules page for creating a new rule
- await expect(page).toHaveURL(/\/rules/);
+ // Should navigate to payee edit page
+ await expect(page).toHaveURL(/\/payees\/.+/);
+
+ // Check that the edit page elements are visible
+ await expect(
+ page.getByRole('heading', { name: 'Edit Payee' }),
+ ).toBeVisible();
+ await expect(page.getByPlaceholder('Payee name')).toBeVisible();
+ await expect(page.getByRole('button', { name: 'Save' })).toBeVisible();
+
await expect(page).toMatchThemeScreenshots();
});
diff --git a/packages/desktop-client/e2e/payees.mobile.test.ts-snapshots/Mobile-Payees-clicking-on-a-payee-opens-payee-edit-page-1-chromium-linux.png b/packages/desktop-client/e2e/payees.mobile.test.ts-snapshots/Mobile-Payees-clicking-on-a-payee-opens-payee-edit-page-1-chromium-linux.png
new file mode 100644
index 0000000000..1aa091f00d
Binary files /dev/null and b/packages/desktop-client/e2e/payees.mobile.test.ts-snapshots/Mobile-Payees-clicking-on-a-payee-opens-payee-edit-page-1-chromium-linux.png differ
diff --git a/packages/desktop-client/e2e/payees.mobile.test.ts-snapshots/Mobile-Payees-clicking-on-a-payee-opens-payee-edit-page-2-chromium-linux.png b/packages/desktop-client/e2e/payees.mobile.test.ts-snapshots/Mobile-Payees-clicking-on-a-payee-opens-payee-edit-page-2-chromium-linux.png
new file mode 100644
index 0000000000..62b779c501
Binary files /dev/null and b/packages/desktop-client/e2e/payees.mobile.test.ts-snapshots/Mobile-Payees-clicking-on-a-payee-opens-payee-edit-page-2-chromium-linux.png differ
diff --git a/packages/desktop-client/e2e/payees.mobile.test.ts-snapshots/Mobile-Payees-clicking-on-a-payee-opens-payee-edit-page-3-chromium-linux.png b/packages/desktop-client/e2e/payees.mobile.test.ts-snapshots/Mobile-Payees-clicking-on-a-payee-opens-payee-edit-page-3-chromium-linux.png
new file mode 100644
index 0000000000..f42ba3cf9e
Binary files /dev/null and b/packages/desktop-client/e2e/payees.mobile.test.ts-snapshots/Mobile-Payees-clicking-on-a-payee-opens-payee-edit-page-3-chromium-linux.png differ
diff --git a/packages/desktop-client/src/components/FinancesApp.tsx b/packages/desktop-client/src/components/FinancesApp.tsx
index 33db725208..547887baea 100644
--- a/packages/desktop-client/src/components/FinancesApp.tsx
+++ b/packages/desktop-client/src/components/FinancesApp.tsx
@@ -270,6 +270,14 @@ export function FinancesApp() {
path="/payees"
element={}
/>
+
+
+
+ }
+ />
}
diff --git a/packages/desktop-client/src/components/mobile/ActionableGridListItem.tsx b/packages/desktop-client/src/components/mobile/ActionableGridListItem.tsx
index e240a37d5e..11ad3e112b 100644
--- a/packages/desktop-client/src/components/mobile/ActionableGridListItem.tsx
+++ b/packages/desktop-client/src/components/mobile/ActionableGridListItem.tsx
@@ -9,7 +9,7 @@ import { useDrag } from '@use-gesture/react';
import { type WithRequired } from 'loot-core/types/util';
type ActionableGridListItemProps = {
- actions?: ReactNode;
+ actions?: ReactNode | ((params: { close: () => void }) => ReactNode);
actionsBackgroundColor?: string;
actionsWidth?: number;
children?: ReactNode;
@@ -131,7 +131,18 @@ export function ActionableGridListItem({
minWidth: actionsWidth,
}}
>
- {actions}
+ {typeof actions === 'function'
+ ? actions({
+ close: () => {
+ api.start({
+ x: 0,
+ onRest: () => {
+ setIsRevealed(false);
+ },
+ });
+ },
+ })
+ : actions}
)}
diff --git a/packages/desktop-client/src/components/mobile/payees/MobilePayeeEditPage.tsx b/packages/desktop-client/src/components/mobile/payees/MobilePayeeEditPage.tsx
new file mode 100644
index 0000000000..3dd06f24f8
--- /dev/null
+++ b/packages/desktop-client/src/components/mobile/payees/MobilePayeeEditPage.tsx
@@ -0,0 +1,152 @@
+import React, { useEffect, useState, useCallback } from 'react';
+import { useTranslation, Trans } from 'react-i18next';
+import { useParams } from 'react-router';
+
+import { Button } from '@actual-app/components/button';
+import { styles } from '@actual-app/components/styles';
+import { Text } from '@actual-app/components/text';
+import { theme } from '@actual-app/components/theme';
+import { View } from '@actual-app/components/view';
+
+import { send } from 'loot-core/platform/client/fetch';
+import { type PayeeEntity } from 'loot-core/types/models';
+
+import { MobileBackButton } from '@desktop-client/components/mobile/MobileBackButton';
+import { InputField } from '@desktop-client/components/mobile/MobileForms';
+import { MobilePageHeader, Page } from '@desktop-client/components/Page';
+import { useNavigate } from '@desktop-client/hooks/useNavigate';
+import { usePayees } from '@desktop-client/hooks/usePayees';
+import { useUndo } from '@desktop-client/hooks/useUndo';
+import { addNotification } from '@desktop-client/notifications/notificationsSlice';
+import { useDispatch } from '@desktop-client/redux';
+
+export function MobilePayeeEditPage() {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const { id } = useParams<{ id: string }>();
+ const dispatch = useDispatch();
+ const { showUndoNotification } = useUndo();
+ const payees = usePayees();
+
+ const [payee, setPayee] = useState(null);
+ const [editedPayeeName, setEditedPayeeName] = useState('');
+ const [isLoading, setIsLoading] = useState(true);
+
+ // Load payee by ID
+ useEffect(() => {
+ if (id) {
+ setIsLoading(true);
+ const foundPayee = payees.find(p => p.id === id);
+ if (foundPayee) {
+ setPayee(foundPayee);
+ setEditedPayeeName(foundPayee.name);
+ setIsLoading(false);
+ } else {
+ // Payee not found, navigate back to payees list
+ navigate('/payees');
+ }
+ }
+ }, [id, payees, navigate]);
+
+ const handleCancel = useCallback(() => {
+ navigate(-1);
+ }, [navigate]);
+
+ const handleSave = useCallback(async () => {
+ if (!payee || !editedPayeeName.trim()) {
+ return;
+ }
+
+ try {
+ await send('payees-batch-change', {
+ updated: [{ id: payee.id, name: editedPayeeName.trim() }],
+ });
+ showUndoNotification({
+ message: t('Payee {{oldName}} renamed to {{newName}}', {
+ oldName: payee.name,
+ newName: editedPayeeName.trim(),
+ }),
+ });
+ navigate('/payees');
+ } catch (error) {
+ console.error('Failed to update payee:', error);
+ dispatch(
+ addNotification({
+ notification: {
+ type: 'error',
+ message: t('Failed to update payee. Please try again.'),
+ },
+ }),
+ );
+ }
+ }, [payee, editedPayeeName, dispatch, showUndoNotification, t, navigate]);
+
+ // Show loading state while fetching payee
+ if (isLoading) {
+ return (
+ }
+ />
+ }
+ padding={0}
+ >
+
+
+ Loading payee...
+
+
+
+ );
+ }
+
+ return (
+ }
+ />
+ }
+ footer={
+
+
+
+ }
+ >
+
+
+
+
+ );
+}
diff --git a/packages/desktop-client/src/components/mobile/payees/MobilePayeesPage.tsx b/packages/desktop-client/src/components/mobile/payees/MobilePayeesPage.tsx
index a2b441f209..df1fed0bbd 100644
--- a/packages/desktop-client/src/components/mobile/payees/MobilePayeesPage.tsx
+++ b/packages/desktop-client/src/components/mobile/payees/MobilePayeesPage.tsx
@@ -43,6 +43,13 @@ export function MobilePayeesPage() {
}, []);
const handlePayeePress = useCallback(
+ (payee: PayeeEntity) => {
+ navigate(`/payees/${payee.id}`);
+ },
+ [navigate],
+ );
+
+ const handlePayeeRuleAction = useCallback(
async (payee: PayeeEntity) => {
// View associated rules for the payee
if ((ruleCounts.get(payee.id) ?? 0) > 0) {
@@ -137,6 +144,7 @@ export function MobilePayeesPage() {
isLoading={isLoading}
onPayeePress={handlePayeePress}
onPayeeDelete={handlePayeeDelete}
+ onPayeeRuleAction={handlePayeeRuleAction}
/>
);
diff --git a/packages/desktop-client/src/components/mobile/payees/PayeesList.tsx b/packages/desktop-client/src/components/mobile/payees/PayeesList.tsx
index 14ebe68d78..bd48a02636 100644
--- a/packages/desktop-client/src/components/mobile/payees/PayeesList.tsx
+++ b/packages/desktop-client/src/components/mobile/payees/PayeesList.tsx
@@ -19,6 +19,7 @@ type PayeesListProps = {
isLoading?: boolean;
onPayeePress: (payee: PayeeEntity) => void;
onPayeeDelete: (payee: PayeeEntity) => void;
+ onPayeeRuleAction: (payee: PayeeEntity) => void;
};
export function PayeesList({
@@ -28,6 +29,7 @@ export function PayeesList({
isLoading = false,
onPayeePress,
onPayeeDelete,
+ onPayeeRuleAction,
}: PayeesListProps) {
const { t } = useTranslation();
@@ -90,6 +92,7 @@ export function PayeesList({
isRuleCountLoading={isRuleCountsLoading}
onAction={() => onPayeePress(payee)}
onDelete={() => onPayeeDelete(payee)}
+ onViewRules={() => onPayeeRuleAction(payee)}
/>
)}
diff --git a/packages/desktop-client/src/components/mobile/payees/PayeesListItem.tsx b/packages/desktop-client/src/components/mobile/payees/PayeesListItem.tsx
index 2c3ceb6fe2..938cf73f82 100644
--- a/packages/desktop-client/src/components/mobile/payees/PayeesListItem.tsx
+++ b/packages/desktop-client/src/components/mobile/payees/PayeesListItem.tsx
@@ -1,4 +1,4 @@
-import React, { memo } from 'react';
+import React from 'react';
import { type GridListItemProps } from 'react-aria-components';
import { Trans, useTranslation } from 'react-i18next';
@@ -6,6 +6,7 @@ import { Button } from '@actual-app/components/button';
import { SvgBookmark } from '@actual-app/components/icons/v1';
import { SpaceBetween } from '@actual-app/components/space-between';
import { theme } from '@actual-app/components/theme';
+import { View } from '@actual-app/components/view';
import { type PayeeEntity } from 'loot-core/types/models';
import { type WithRequired } from 'loot-core/types/util';
@@ -17,13 +18,15 @@ type PayeesListItemProps = {
ruleCount: number;
isRuleCountLoading?: boolean;
onDelete: () => void;
+ onViewRules: () => void;
} & WithRequired, 'value'>;
-export const PayeesListItem = memo(function PayeeListItem({
+export function PayeesListItem({
value: payee,
ruleCount,
isRuleCountLoading,
onDelete,
+ onViewRules,
...props
}: PayeesListItemProps) {
const { t } = useTranslation();
@@ -37,18 +40,38 @@ export const PayeesListItem = memo(function PayeeListItem({
id={payee.id}
value={payee}
textValue={label}
+ actionsWidth={200}
actions={
!payee.transfer_acct && (
-
+
+
+
+
)
}
{...props}
@@ -112,4 +135,4 @@ export const PayeesListItem = memo(function PayeeListItem({
);
-});
+}
diff --git a/packages/desktop-client/src/components/responsive/narrow.ts b/packages/desktop-client/src/components/responsive/narrow.ts
index 14f722f2f6..99f0e2a2d9 100644
--- a/packages/desktop-client/src/components/responsive/narrow.ts
+++ b/packages/desktop-client/src/components/responsive/narrow.ts
@@ -8,5 +8,7 @@ export { MobileRuleEditPage as RuleEdit } from '../mobile/rules/MobileRuleEditPa
export { CategoryPage as Category } from '../mobile/budget/CategoryPage';
export { MobilePayeesPage as Payees } from '../mobile/payees/MobilePayeesPage';
+export { MobilePayeeEditPage as PayeeEdit } from '../mobile/payees/MobilePayeeEditPage';
+
export { MobileBankSyncPage as BankSync } from '../mobile/banksync/MobileBankSyncPage';
export { MobileBankSyncAccountEditPage as BankSyncAccountEdit } from '../mobile/banksync/MobileBankSyncAccountEditPage';
diff --git a/packages/desktop-client/src/components/responsive/wide.ts b/packages/desktop-client/src/components/responsive/wide.ts
index d9b8d59f24..2dcd844bb9 100644
--- a/packages/desktop-client/src/components/responsive/wide.ts
+++ b/packages/desktop-client/src/components/responsive/wide.ts
@@ -10,6 +10,8 @@ export { Account } from '../accounts/Account';
export { ManageRulesPage as Rules } from '../ManageRulesPage';
export { ManageRulesPage as RuleEdit } from '../ManageRulesPage';
export { ManagePayeesPage as Payees } from '../payees/ManagePayeesPage';
+export { ManagePayeesPage as PayeeEdit } from '../payees/ManagePayeesPage';
+
export { BankSync } from '../banksync';
export { UserDirectoryPage } from '../admin/UserDirectory/UserDirectoryPage';
diff --git a/upcoming-release-notes/5874.md b/upcoming-release-notes/5874.md
new file mode 100644
index 0000000000..2a27238b09
--- /dev/null
+++ b/upcoming-release-notes/5874.md
@@ -0,0 +1,6 @@
+---
+category: Enhancements
+authors: [MatissJanis]
+---
+
+Mobile payees: add edit payee functionality