diff --git a/frontend/pshared/lib/api/responses/wallet_balance.dart b/frontend/pshared/lib/api/responses/wallet_balance.dart new file mode 100644 index 0000000..ec1c599 --- /dev/null +++ b/frontend/pshared/lib/api/responses/wallet_balance.dart @@ -0,0 +1,16 @@ +import 'package:json_annotation/json_annotation.dart'; + +import 'package:pshared/data/dto/wallet/balance.dart'; + +part 'wallet_balance.g.dart'; + + +@JsonSerializable(explicitToJson: true) +class WalletBalanceResponse { + final WalletBalanceDTO balance; + + const WalletBalanceResponse({required this.balance}); + + factory WalletBalanceResponse.fromJson(Map json) => _$WalletBalanceResponseFromJson(json); + Map toJson() => _$WalletBalanceResponseToJson(this); +} diff --git a/frontend/pshared/lib/api/responses/wallets.dart b/frontend/pshared/lib/api/responses/wallets.dart new file mode 100644 index 0000000..e1019ce --- /dev/null +++ b/frontend/pshared/lib/api/responses/wallets.dart @@ -0,0 +1,16 @@ +import 'package:json_annotation/json_annotation.dart'; + +import 'package:pshared/data/dto/wallet/wallet.dart'; + +part 'wallets.g.dart'; + + +@JsonSerializable(explicitToJson: true) +class WalletsResponse { + final List wallets; + + const WalletsResponse({required this.wallets}); + + factory WalletsResponse.fromJson(Map json) => _$WalletsResponseFromJson(json); + Map toJson() => _$WalletsResponseToJson(this); +} diff --git a/frontend/pshared/lib/data/dto/wallet/asset.dart b/frontend/pshared/lib/data/dto/wallet/asset.dart new file mode 100644 index 0000000..a747bcb --- /dev/null +++ b/frontend/pshared/lib/data/dto/wallet/asset.dart @@ -0,0 +1,20 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'asset.g.dart'; + + +@JsonSerializable() +class WalletAssetDTO { + final String chain; + final String tokenSymbol; + final String contractAddress; + + const WalletAssetDTO({ + required this.chain, + required this.tokenSymbol, + required this.contractAddress, + }); + + factory WalletAssetDTO.fromJson(Map json) => _$WalletAssetDTOFromJson(json); + Map toJson() => _$WalletAssetDTOToJson(this); +} diff --git a/frontend/pshared/lib/data/dto/wallet/balance.dart b/frontend/pshared/lib/data/dto/wallet/balance.dart new file mode 100644 index 0000000..4be200e --- /dev/null +++ b/frontend/pshared/lib/data/dto/wallet/balance.dart @@ -0,0 +1,24 @@ +import 'package:json_annotation/json_annotation.dart'; + +import 'package:pshared/data/dto/wallet/money.dart'; + +part 'balance.g.dart'; + + +@JsonSerializable() +class WalletBalanceDTO { + final MoneyDTO? available; + final MoneyDTO? pendingInbound; + final MoneyDTO? pendingOutbound; + final String? calculatedAt; + + const WalletBalanceDTO({ + required this.available, + required this.pendingInbound, + required this.pendingOutbound, + required this.calculatedAt, + }); + + factory WalletBalanceDTO.fromJson(Map json) => _$WalletBalanceDTOFromJson(json); + Map toJson() => _$WalletBalanceDTOToJson(this); +} diff --git a/frontend/pshared/lib/data/dto/wallet/money.dart b/frontend/pshared/lib/data/dto/wallet/money.dart new file mode 100644 index 0000000..17bf8dc --- /dev/null +++ b/frontend/pshared/lib/data/dto/wallet/money.dart @@ -0,0 +1,18 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'money.g.dart'; + + +@JsonSerializable() +class MoneyDTO { + final String amount; + final String currency; + + const MoneyDTO({ + required this.amount, + required this.currency, + }); + + factory MoneyDTO.fromJson(Map json) => _$MoneyDTOFromJson(json); + Map toJson() => _$MoneyDTOToJson(this); +} diff --git a/frontend/pshared/lib/data/dto/wallet/wallet.dart b/frontend/pshared/lib/data/dto/wallet/wallet.dart new file mode 100644 index 0000000..fe740ea --- /dev/null +++ b/frontend/pshared/lib/data/dto/wallet/wallet.dart @@ -0,0 +1,34 @@ +import 'package:json_annotation/json_annotation.dart'; + +import 'package:pshared/data/dto/wallet/asset.dart'; + +part 'wallet.g.dart'; + + +@JsonSerializable() +class WalletDTO { + final String walletRef; + final String organizationRef; + final String ownerRef; + final WalletAssetDTO asset; + final String depositAddress; + final String status; + final Map? metadata; + final String? createdAt; + final String? updatedAt; + + const WalletDTO({ + required this.walletRef, + required this.organizationRef, + required this.ownerRef, + required this.asset, + required this.depositAddress, + required this.status, + this.metadata, + this.createdAt, + this.updatedAt, + }); + + factory WalletDTO.fromJson(Map json) => _$WalletDTOFromJson(json); + Map toJson() => _$WalletDTOToJson(this); +} diff --git a/frontend/pshared/lib/data/mapper/wallet/balance.dart b/frontend/pshared/lib/data/mapper/wallet/balance.dart new file mode 100644 index 0000000..0060fa0 --- /dev/null +++ b/frontend/pshared/lib/data/mapper/wallet/balance.dart @@ -0,0 +1,15 @@ +import 'package:pshared/data/dto/wallet/balance.dart'; +import 'package:pshared/data/mapper/wallet/money.dart'; +import 'package:pshared/models/wallet/balance.dart'; + + +extension WalletBalanceDTOMapper on WalletBalanceDTO { + WalletBalance toDomain() => WalletBalance( + available: available?.toDomain(), + pendingInbound: pendingInbound?.toDomain(), + pendingOutbound: pendingOutbound?.toDomain(), + calculatedAt: (calculatedAt == null || calculatedAt!.isEmpty) + ? null + : DateTime.tryParse(calculatedAt!), + ); +} diff --git a/frontend/pshared/lib/data/mapper/wallet/money.dart b/frontend/pshared/lib/data/mapper/wallet/money.dart new file mode 100644 index 0000000..72a6f2e --- /dev/null +++ b/frontend/pshared/lib/data/mapper/wallet/money.dart @@ -0,0 +1,10 @@ +import 'package:pshared/data/dto/wallet/money.dart'; +import 'package:pshared/models/wallet/money.dart'; + + +extension MoneyDTOMapper on MoneyDTO { + WalletMoney toDomain() => WalletMoney( + amount: amount, + currency: currency, + ); +} diff --git a/frontend/pshared/lib/data/mapper/wallet/response.dart b/frontend/pshared/lib/data/mapper/wallet/response.dart new file mode 100644 index 0000000..820ad55 --- /dev/null +++ b/frontend/pshared/lib/data/mapper/wallet/response.dart @@ -0,0 +1,14 @@ +import 'package:pshared/api/responses/wallet_balance.dart'; +import 'package:pshared/api/responses/wallets.dart'; +import 'package:pshared/data/mapper/wallet/balance.dart'; +import 'package:pshared/data/mapper/wallet/wallet.dart'; +import 'package:pshared/models/wallet/balance.dart'; +import 'package:pshared/models/wallet/wallet.dart'; + +extension WalletsResponseMapper on WalletsResponse { + List toDomain() => wallets.map((w) => w.toDomain()).toList(); +} + +extension WalletBalanceResponseMapper on WalletBalanceResponse { + WalletBalance toDomain() => balance.toDomain(); +} diff --git a/frontend/pshared/lib/data/mapper/wallet/wallet.dart b/frontend/pshared/lib/data/mapper/wallet/wallet.dart new file mode 100644 index 0000000..9635fc4 --- /dev/null +++ b/frontend/pshared/lib/data/mapper/wallet/wallet.dart @@ -0,0 +1,26 @@ +import 'package:pshared/data/dto/wallet/balance.dart'; +import 'package:pshared/data/dto/wallet/wallet.dart'; +import 'package:pshared/data/mapper/wallet/balance.dart'; +import 'package:pshared/data/mapper/wallet/money.dart'; +import 'package:pshared/models/wallet/wallet.dart'; + + +extension WalletDTOMapper on WalletDTO { + WalletModel toDomain({WalletBalanceDTO? balance}) => WalletModel( + walletRef: walletRef, + organizationRef: organizationRef, + ownerRef: ownerRef, + asset: WalletAsset( + chain: asset.chain, + tokenSymbol: asset.tokenSymbol, + contractAddress: asset.contractAddress, + ), + depositAddress: depositAddress, + status: status, + metadata: metadata, + createdAt: (createdAt == null || createdAt!.isEmpty) ? null : DateTime.tryParse(createdAt!), + updatedAt: (updatedAt == null || updatedAt!.isEmpty) ? null : DateTime.tryParse(updatedAt!), + balance: balance?.toDomain(), + availableMoney: balance?.available?.toDomain(), + ); +} diff --git a/frontend/pshared/lib/models/wallet/balance.dart b/frontend/pshared/lib/models/wallet/balance.dart new file mode 100644 index 0000000..d0f8830 --- /dev/null +++ b/frontend/pshared/lib/models/wallet/balance.dart @@ -0,0 +1,16 @@ +import 'package:pshared/models/wallet/money.dart'; + + +class WalletBalance { + final WalletMoney? available; + final WalletMoney? pendingInbound; + final WalletMoney? pendingOutbound; + final DateTime? calculatedAt; + + const WalletBalance({ + required this.available, + required this.pendingInbound, + required this.pendingOutbound, + required this.calculatedAt, + }); +} diff --git a/frontend/pshared/lib/models/wallet/money.dart b/frontend/pshared/lib/models/wallet/money.dart new file mode 100644 index 0000000..305ec20 --- /dev/null +++ b/frontend/pshared/lib/models/wallet/money.dart @@ -0,0 +1,9 @@ +class WalletMoney { + final String amount; + final String currency; + + const WalletMoney({ + required this.amount, + required this.currency, + }); +} diff --git a/frontend/pshared/lib/models/wallet/wallet.dart b/frontend/pshared/lib/models/wallet/wallet.dart new file mode 100644 index 0000000..188e089 --- /dev/null +++ b/frontend/pshared/lib/models/wallet/wallet.dart @@ -0,0 +1,62 @@ +import 'package:pshared/models/wallet/balance.dart'; +import 'package:pshared/models/wallet/money.dart'; + + +class WalletAsset { + final String chain; + final String tokenSymbol; + final String contractAddress; + + const WalletAsset({ + required this.chain, + required this.tokenSymbol, + required this.contractAddress, + }); +} + +class WalletModel { + final String walletRef; + final String organizationRef; + final String ownerRef; + final WalletAsset asset; + final String depositAddress; + final String status; + final Map? metadata; + final DateTime? createdAt; + final DateTime? updatedAt; + final WalletBalance? balance; + final WalletMoney? availableMoney; + + const WalletModel({ + required this.walletRef, + required this.organizationRef, + required this.ownerRef, + required this.asset, + required this.depositAddress, + required this.status, + this.metadata, + this.createdAt, + this.updatedAt, + this.balance, + this.availableMoney, + }); + + WalletModel copyWith({ + WalletBalance? balance, + WalletMoney? availableMoney, + }) { + return WalletModel( + walletRef: walletRef, + organizationRef: organizationRef, + ownerRef: ownerRef, + asset: asset, + depositAddress: depositAddress, + status: status, + metadata: metadata, + createdAt: createdAt, + updatedAt: updatedAt, + balance: balance ?? this.balance, + availableMoney: availableMoney ?? this.availableMoney, + ); + } +} diff --git a/frontend/pshared/lib/service/services.dart b/frontend/pshared/lib/service/services.dart index fcf45ee..3c327fc 100644 --- a/frontend/pshared/lib/service/services.dart +++ b/frontend/pshared/lib/service/services.dart @@ -7,6 +7,7 @@ class Services { static const String organization = 'organizations'; static const String permission = 'permissions'; static const String storage = 'storage'; + static const String chainWallets = 'chain_wallets'; static const String amplitude = 'amplitude'; static const String clients = 'clients'; diff --git a/frontend/pshared/lib/service/wallet.dart b/frontend/pshared/lib/service/wallet.dart new file mode 100644 index 0000000..e3d554b --- /dev/null +++ b/frontend/pshared/lib/service/wallet.dart @@ -0,0 +1,31 @@ +import 'package:pshared/api/responses/wallet_balance.dart'; +import 'package:pshared/api/responses/wallets.dart'; +import 'package:pshared/data/mapper/wallet/response.dart'; +import 'package:pshared/models/wallet/balance.dart'; +import 'package:pshared/models/wallet/wallet.dart'; +import 'package:pshared/service/authorization/service.dart'; +import 'package:pshared/service/services.dart'; + + +class WalletService { + static const String _objectType = Services.chainWallets; + + static Future> list(String organizationRef) async { + final json = await AuthorizationService.getGETResponse( + _objectType, + '/$organizationRef', + ); + return WalletsResponse.fromJson(json).toDomain(); + } + + static Future getBalance({ + required String organizationRef, + required String walletRef, + }) async { + final json = await AuthorizationService.getGETResponse( + _objectType, + '/$organizationRef/$walletRef/balance', + ); + return WalletBalanceResponse.fromJson(json).toDomain(); + } +} diff --git a/frontend/pweb/lib/app/router/router.dart b/frontend/pweb/lib/app/router/router.dart index 33e7670..2a693e5 100644 --- a/frontend/pweb/lib/app/router/router.dart +++ b/frontend/pweb/lib/app/router/router.dart @@ -1,5 +1,9 @@ +import 'package:provider/provider.dart'; + import 'package:go_router/go_router.dart'; +import 'package:pshared/provider/organizations.dart'; + import 'package:pweb/app/router/pages.dart'; import 'package:pweb/app/router/page_params.dart'; import 'package:pweb/pages/2fa/page.dart'; @@ -32,7 +36,11 @@ GoRouter createRouter() => GoRouter( name: Pages.sfactor.name, path: routerPage(Pages.sfactor), builder: (context, _) => TwoFactorCodePage( - onVerificationSuccess: () => context.goNamed(Pages.dashboard.name), + onVerificationSuccess: () { + // trigger organization load + context.read().load(); + context.goNamed(Pages.dashboard.name); + }, ), ), GoRoute( diff --git a/frontend/pweb/lib/data/mappers/wallet_ui.dart b/frontend/pweb/lib/data/mappers/wallet_ui.dart new file mode 100644 index 0000000..0e3d962 --- /dev/null +++ b/frontend/pweb/lib/data/mappers/wallet_ui.dart @@ -0,0 +1,26 @@ +import 'package:pshared/models/wallet/wallet.dart' as domain; + +import 'package:pweb/models/currency.dart'; +import 'package:pweb/models/wallet.dart'; + + +extension WalletUiMapper on domain.WalletModel { + Wallet toUi() { + final amountStr = availableMoney?.amount ?? balance?.available?.amount ?? '0'; + final currencyStr = availableMoney?.currency ?? balance?.available?.currency ?? Currency.usd.toString().toUpperCase(); + final parsedAmount = double.tryParse(amountStr) ?? 0; + final currency = Currency.values.firstWhere( + (c) => c.name.toUpperCase() == currencyStr.toUpperCase(), + orElse: () => Currency.usd, + ); + return Wallet( + id: walletRef, + walletUserID: walletRef, + name: metadata?['name'] ?? walletRef, + balance: parsedAmount, + currency: currency, + isHidden: true, + calculatedAt: balance?.calculatedAt ?? DateTime.now(), + ); + } +} diff --git a/frontend/pweb/lib/main.dart b/frontend/pweb/lib/main.dart index c53b4bd..8ce1032 100644 --- a/frontend/pweb/lib/main.dart +++ b/frontend/pweb/lib/main.dart @@ -72,8 +72,9 @@ void main() async { ChangeNotifierProvider( create: (_) => PaymentMethodsProvider(service: MockPaymentMethodsService())..loadMethods(), ), - ChangeNotifierProvider( - create: (_) => WalletsProvider(MockWalletsService())..loadData(), + ChangeNotifierProxyProvider( + create: (_) => WalletsProvider(ApiWalletsService()), + update: (context, organizations, provider) => provider!..update(organizations), ), ChangeNotifierProvider( create: (_) => WalletTransactionsProvider(MockWalletTransactionsService())..load(), diff --git a/frontend/pweb/lib/models/wallet.dart b/frontend/pweb/lib/models/wallet.dart index aef5133..1d663a7 100644 --- a/frontend/pweb/lib/models/wallet.dart +++ b/frontend/pweb/lib/models/wallet.dart @@ -8,6 +8,7 @@ class Wallet { final double balance; final Currency currency; final bool isHidden; + final DateTime calculatedAt; Wallet({ required this.id, @@ -15,6 +16,7 @@ class Wallet { required this.name, required this.balance, required this.currency, + required this.calculatedAt, this.isHidden = true, }); @@ -25,14 +27,13 @@ class Wallet { Currency? currency, String? walletUserID, bool? isHidden, - }) { - return Wallet( - id: id ?? this.id, - name: name ?? this.name, - balance: balance ?? this.balance, - currency: currency ?? this.currency, - walletUserID: walletUserID ?? this.walletUserID, - isHidden: isHidden ?? this.isHidden, - ); - } + }) => Wallet( + id: id ?? this.id, + name: name ?? this.name, + balance: balance ?? this.balance, + currency: currency ?? this.currency, + walletUserID: walletUserID ?? this.walletUserID, + isHidden: isHidden ?? this.isHidden, + calculatedAt: calculatedAt, + ); } \ No newline at end of file diff --git a/frontend/pweb/lib/pages/login/form.dart b/frontend/pweb/lib/pages/login/form.dart index bf230b1..1eb3bcc 100644 --- a/frontend/pweb/lib/pages/login/form.dart +++ b/frontend/pweb/lib/pages/login/form.dart @@ -44,6 +44,7 @@ class _LoginFormState extends State { locale: context.read().locale.languageCode, ); if (outcome.isPending) { + // TODO: fix context usage navigateAndReplace(context, Pages.sfactor); } else { onLogin(); diff --git a/frontend/pweb/lib/pages/payout_page/wallet/edit/header.dart b/frontend/pweb/lib/pages/payout_page/wallet/edit/header.dart index d91e449..1693c9e 100644 --- a/frontend/pweb/lib/pages/payout_page/wallet/edit/header.dart +++ b/frontend/pweb/lib/pages/payout_page/wallet/edit/header.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:pweb/providers/wallets.dart'; +import 'package:pweb/widgets/error/snackbar.dart'; class WalletEditHeader extends StatefulWidget { @@ -85,10 +86,11 @@ class _WalletEditHeaderState extends State { icon: const Icon(Icons.check), color: theme.colorScheme.primary, onPressed: () async { - provider.updateName(wallet.id, _controller.text); - await provider.updateWallet(wallet.copyWith(name: _controller.text)); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Wallet name saved')), + await executeActionWithNotification( + context: context, + action: () async => await provider.updateWallet(wallet.copyWith(name: _controller.text)), + errorMessage: 'Failed to update wallet name', + successMessage: 'Wallet name saved', ); setState(() { _isEditing = false; diff --git a/frontend/pweb/lib/providers/wallets.dart b/frontend/pweb/lib/providers/wallets.dart index 86782c8..6a673ce 100644 --- a/frontend/pweb/lib/providers/wallets.dart +++ b/frontend/pweb/lib/providers/wallets.dart @@ -1,12 +1,16 @@ import 'package:flutter/material.dart'; -import 'package:pweb/models/wallet.dart'; -import 'package:pweb/services/wallets.dart'; +import 'package:pshared/provider/organizations.dart'; import 'package:pshared/provider/resource.dart'; import 'package:pshared/utils/exception.dart'; +import 'package:pweb/models/wallet.dart'; +import 'package:pweb/services/wallets.dart'; + + class WalletsProvider with ChangeNotifier { final WalletsService _service; + late OrganizationsProvider _organizations; WalletsProvider(this._service); @@ -25,6 +29,15 @@ class WalletsProvider with ChangeNotifier { bool _isRefreshingBalances = false; bool get isRefreshingBalances => _isRefreshingBalances; + void update(OrganizationsProvider organizations) { + _organizations = organizations; + if (_organizations.isOrganizationSet) loadWalletsWithBalances(); + } + + Future updateWallet(Wallet newWallet) { + throw Exception('update wallet is not implemented'); + } + void selectWallet(Wallet wallet) { _selectedWallet = wallet; notifyListeners(); @@ -33,11 +46,11 @@ class WalletsProvider with ChangeNotifier { Future loadWalletsWithBalances() async { _setResource(_resource.copyWith(isLoading: true, error: null)); try { - final base = await _service.getWallets(); + final base = await _service.getWallets(_organizations.current.id); final withBalances = []; for (final wallet in base) { try { - final balance = await _service.getBalance(wallet.id); + final balance = await _service.getBalance(_organizations.current.id, wallet.id); withBalances.add(wallet.copyWith(balance: balance)); } catch (e) { _setResource(_resource.copyWith(error: toException(e))); @@ -58,7 +71,7 @@ class WalletsProvider with ChangeNotifier { try { final updated = []; for (final wallet in wallets) { - final balance = await _service.getBalance(wallet.id); + final balance = await _service.getBalance(_organizations.current.id, wallet.id); updated.add(wallet.copyWith(balance: balance)); } _setResource(_resource.copyWith(data: updated)); diff --git a/frontend/pweb/lib/services/wallets.dart b/frontend/pweb/lib/services/wallets.dart index 7b9cc5e..3dee265 100644 --- a/frontend/pweb/lib/services/wallets.dart +++ b/frontend/pweb/lib/services/wallets.dart @@ -1,33 +1,28 @@ +import 'package:pshared/service/wallet.dart' as shared_wallet_service; + import 'package:pweb/models/currency.dart'; import 'package:pweb/models/wallet.dart'; +import 'package:pweb/data/mappers/wallet_ui.dart'; abstract class WalletsService { - Future> getWallets(); - Future getBalance(String walletRef); + Future> getWallets(String organizationRef); + Future getBalance(String organizationRef, String walletRef); } class MockWalletsService implements WalletsService { final List _wallets = [ - Wallet(id: '1124', walletUserID: 'WA-12345667', name: 'Main Wallet', balance: 10000000.0, currency: Currency.rub), - Wallet(id: '2124', walletUserID: 'WA-76654321', name: 'Savings', balance: 2500.5, currency: Currency.usd), + Wallet(id: '1124', walletUserID: 'WA-12345667', name: 'Main Wallet', balance: 10000000.0, currency: Currency.rub, calculatedAt: DateTime.now()), + Wallet(id: '2124', walletUserID: 'WA-76654321', name: 'Savings', balance: 2500.5, currency: Currency.usd, calculatedAt: DateTime.now()), ]; @override - Future> getWallets() async { + Future> getWallets(String _) async { return _wallets; } @override - Future getWallet(String walletId) async { - return _wallets.firstWhere( - (wallet) => wallet.id == walletId, - orElse: () => throw Exception('Wallet not found'), - ); - } - - @override - Future getBalance(String walletRef) async { + Future getBalance(String _, String walletRef) async { final wallet = _wallets.firstWhere( (w) => w.id == walletRef, orElse: () => throw Exception('Wallet not found'), @@ -35,3 +30,21 @@ class MockWalletsService implements WalletsService { return wallet.balance; } } + +class ApiWalletsService implements WalletsService { + @override + Future> getWallets(String organizationRef) async { + final models = await shared_wallet_service.WalletService.list(organizationRef); + return models.map((m) => m.toUi()).toList(); + } + + @override + Future getBalance(String organizationRef, String walletRef) async { + final balance = await shared_wallet_service.WalletService.getBalance( + organizationRef: organizationRef, + walletRef: walletRef, + ); + final amount = balance.available?.amount; + return amount == null ? 0 : double.tryParse(amount) ?? 0; + } +} diff --git a/frontend/pweb/lib/widgets/error/snackbar.dart b/frontend/pweb/lib/widgets/error/snackbar.dart index 9be921e..fd37120 100644 --- a/frontend/pweb/lib/widgets/error/snackbar.dart +++ b/frontend/pweb/lib/widgets/error/snackbar.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:pweb/utils/error_handler.dart'; +import 'package:pweb/utils/snackbar.dart'; import 'package:pweb/widgets/error/content.dart'; import 'package:pweb/generated/i18n/app_localizations.dart'; @@ -52,13 +53,18 @@ Future executeActionWithNotification({ required BuildContext context, required Future Function() action, required String errorMessage, + String? successMessage, int delaySeconds = 3, }) async { final scaffoldMessenger = ScaffoldMessenger.of(context); final localizations = AppLocalizations.of(context)!; try { - return await action(); + final res = await action(); + if (successMessage != null) { + notifyUserX(scaffoldMessenger, successMessage, delaySeconds: delaySeconds); + } + return res; } catch (e) { // Report the error using your existing notifier. notifyUserOfErrorX(