diff --git a/lib/http/http.dart b/lib/http/http.dart index 6896afa..980aeb4 100644 --- a/lib/http/http.dart +++ b/lib/http/http.dart @@ -61,17 +61,18 @@ class Http { /// Issues a POST request without authentication headers and returns /// the raw [Response] object. Future postRaw(String url, {dynamic data}) async { + Map headers = await getHeaders(); final response = await dio.post( baseUrl + url, data: data, - options: Options(headers: {'Content-Type': 'application/json'}), + options: Options(headers: headers), ); logger.i(response.data.toString()); return response; } Future post(String url, dynamic data) async { - Map headers = {'Content-Type': 'application/json'}; + Map headers = await getHeaders(); Response response; response = await dio.post( @@ -130,10 +131,7 @@ class Http { ) async { final formData = FormData.fromMap({ ...fields, - fileFieldName: MultipartFile.fromBytes( - fileBytes, - filename: fileName, - ), + fileFieldName: MultipartFile.fromBytes(fileBytes, filename: fileName), }); final prefs = await SharedPreferences.getInstance(); @@ -142,9 +140,7 @@ class Http { final response = await dio.post( baseUrl + url, data: formData, - options: Options(headers: { - 'Authorization': 'Bearer $token', - }), + options: Options(headers: {'Authorization': 'Bearer $token'}), ); logger.i(response.data.toString()); return response.data; diff --git a/lib/main.dart b/lib/main.dart index 00cb26d..5032afb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,6 +7,7 @@ import 'package:qpay/url_strategy.dart'; import 'package:qpay/screens/transaction_controller.dart'; import 'package:qpay/screens/onboarding/onboarding_controller.dart'; import 'package:qpay/providers/splash_provider.dart'; +import 'package:qpay/screens/workspaces/workspace_provider.dart'; import 'package:qpay/theme/app_theme.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:firebase_core/firebase_core.dart'; @@ -53,6 +54,9 @@ void main() async { ChangeNotifierProvider( create: (_) => SplashProvider(), ), + ChangeNotifierProvider( + create: (_) => WorkspaceProvider(), + ), ], child: const MyApp(), ), diff --git a/lib/routes.dart b/lib/routes.dart index ece0b53..e1b5fd9 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -29,6 +29,7 @@ import 'package:qpay/screens/profile_screen.dart'; import 'package:qpay/screens/receipt/receipt_screen.dart'; import 'package:qpay/screens/recipient/recipients_screen.dart'; import 'package:qpay/screens/splash_screen.dart'; +import 'package:qpay/screens/workspaces/workspace_screen.dart'; /// Tracks whether the splash animation is complete so that /// GoRouter's redirect can show the splash first, then release @@ -135,9 +136,10 @@ GoRouter buildRouter() { return authState.requireAuth(path); } - // Splash → original route or home. + // Splash → workspace selector (which may auto-redirect to home + // if only one workspace exists). if (path == '/splash') { - return splashState.intendedRoute ?? '/'; + return '/workspace'; } } @@ -153,12 +155,13 @@ GoRouter buildRouter() { ShellRoute( builder: (context, state, child) { final location = GoRouterState.of(context).uri.toString(); - if (location.startsWith('/onboarding/')) { + if (location.startsWith('/onboarding/') || location == '/workspace') { return child; } return ScaffoldWithNavBar(child: child); }, routes: [ + GoRoute(path: '/workspace', builder: (context, state) => const WorkspaceScreen()), GoRoute(path: '/', builder: (context, state) => const HomeScreen()), GoRoute( path: '/home', @@ -229,8 +232,7 @@ GoRouter buildRouter() { ), GoRoute( path: '/groups/create-from-file', - builder: (context, state) => - const CreateGroupFromFileScreen(), + builder: (context, state) => const CreateGroupFromFileScreen(), ), GoRoute( path: '/groups/:groupId', diff --git a/lib/screens/accounts/account_provider.dart b/lib/screens/accounts/account_provider.dart index 955c02a..48af6f7 100644 --- a/lib/screens/accounts/account_provider.dart +++ b/lib/screens/accounts/account_provider.dart @@ -38,14 +38,17 @@ class AccountProvider extends ChangeNotifier { super.dispose(); } - Future> fetchAccount(String phoneNumber) async { + Future> fetchAccount( + String? workspaceId, + String currency, + ) async { _isLoadingAccount = true; _accountError = null; notifyListeners(); try { Map response = await http.get( - '/accounts/phone/$phoneNumber', + '/accounts/$workspaceId/balance/$currency', ); _account = AccountModel.fromJson(response); @@ -69,7 +72,8 @@ class AccountProvider extends ChangeNotifier { } Future> fetchStatement({ - required String phoneNumber, + required String workspaceId, + required String currency, required String startDate, required String endDate, }) async { @@ -79,7 +83,7 @@ class AccountProvider extends ChangeNotifier { try { Map response = await http.get( - '/accounts/phone/$phoneNumber/statement' + '/accounts/$workspaceId/statement/$currency' '?startDate=$startDate&endDate=$endDate', ); diff --git a/lib/screens/accounts/widgets/account_balance_widget.dart b/lib/screens/accounts/widgets/account_balance_widget.dart index 3fe1aa8..ffe16c2 100644 --- a/lib/screens/accounts/widgets/account_balance_widget.dart +++ b/lib/screens/accounts/widgets/account_balance_widget.dart @@ -23,7 +23,7 @@ class _AccountBalanceWidgetState extends State { late AccountProvider _accountProvider; bool _isLoggedIn = false; - String? _phoneNumber; + String? _workspaceId; AccountModel? _usdAccount; AccountModel? _zwgAccount; @@ -31,6 +31,7 @@ class _AccountBalanceWidgetState extends State { bool _isLoadingZwG = false; String? _usdError; String? _zwgError; + bool _showAccountError = false; @override void initState() { @@ -55,9 +56,9 @@ class _AccountBalanceWidgetState extends State { }); } - _phoneNumber = prefs.getString('phone'); + _workspaceId = prefs.getString('workspaceId'); - if (_phoneNumber == null || _phoneNumber!.isEmpty) { + if (_workspaceId == null || _workspaceId!.isEmpty) { return; } @@ -67,9 +68,7 @@ class _AccountBalanceWidgetState extends State { }); // Fetch USD balance - final usdResult = await _accountProvider.fetchAccount( - 'USD$_phoneNumber', - ); + final usdResult = await _accountProvider.fetchAccount(_workspaceId, 'USD'); if (mounted) { setState(() { _isLoadingUsd = false; @@ -83,9 +82,7 @@ class _AccountBalanceWidgetState extends State { } // Fetch ZWG balance - final zwgResult = await _accountProvider.fetchAccount( - 'ZWG$_phoneNumber', - ); + final zwgResult = await _accountProvider.fetchAccount(_workspaceId, 'ZWG'); if (mounted) { setState(() { _isLoadingZwG = false; @@ -104,25 +101,23 @@ class _AccountBalanceWidgetState extends State { !zwgResult.isSuccess && _isLoggedIn) { setState(() { - _isLoggedIn = false; + _showAccountError = true; }); } } Future _refreshBalances() async { final prefs = await SharedPreferences.getInstance(); - _phoneNumber = prefs.getString('phone'); + _workspaceId = prefs.getString('workspaceId'); - if (_phoneNumber == null || _phoneNumber!.isEmpty) return; + if (_workspaceId == null || _workspaceId!.isEmpty) return; setState(() { _isLoadingUsd = true; _isLoadingZwG = true; }); - final usdResult = await _accountProvider.fetchAccount( - 'USD$_phoneNumber', - ); + final usdResult = await _accountProvider.fetchAccount(_workspaceId, 'USD'); if (mounted) { setState(() { _isLoadingUsd = false; @@ -135,9 +130,7 @@ class _AccountBalanceWidgetState extends State { }); } - final zwgResult = await _accountProvider.fetchAccount( - 'ZWG$_phoneNumber', - ); + final zwgResult = await _accountProvider.fetchAccount(_workspaceId, 'ZWG'); if (mounted) { setState(() { _isLoadingZwG = false; @@ -155,7 +148,7 @@ class _AccountBalanceWidgetState extends State { !zwgResult.isSuccess && _isLoggedIn) { setState(() { - _isLoggedIn = false; + _showAccountError = true; }); } } @@ -169,10 +162,47 @@ class _AccountBalanceWidgetState extends State { return _buildLoginPrompt(theme, isDark); } - if (_phoneNumber == null || _phoneNumber!.isEmpty) { + if (_workspaceId == null || _workspaceId!.isEmpty) { return const SizedBox.shrink(); } + if (_showAccountError) { + return Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Container( + width: double.infinity, + padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 18), + decoration: BoxDecoration( + color: theme.colorScheme.error.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: theme.colorScheme.error.withValues(alpha: 0.3), + width: 1, + ), + ), + child: Row( + children: [ + Icon( + Icons.error_outline_rounded, + color: theme.colorScheme.error.withValues(alpha: 0.7), + size: 16, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + 'Unable to load account balances. Please try refreshing.', + style: TextStyle( + fontSize: 12, + color: theme.colorScheme.error.withValues(alpha: 0.7), + ), + ), + ), + ], + ), + ), + ); + } + return Padding( padding: const EdgeInsets.only(bottom: 12), child: Column( @@ -231,9 +261,7 @@ class _AccountBalanceWidgetState extends State { : theme.colorScheme.primary, ), label: Text( - _isLoadingUsd || _isLoadingZwG - ? 'Refreshing...' - : 'Refresh', + _isLoadingUsd || _isLoadingZwG ? 'Refreshing...' : 'Refresh', style: TextStyle( fontSize: 11, fontWeight: FontWeight.w600, @@ -301,7 +329,10 @@ class _AccountBalanceWidgetState extends State { style: ElevatedButton.styleFrom( backgroundColor: theme.colorScheme.primary, foregroundColor: Colors.white, - padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), + padding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), @@ -343,18 +374,17 @@ class _AccountBalanceWidgetState extends State { begin: Alignment.topLeft, end: Alignment.bottomRight, ), - boxShadow: [ - BoxShadow( - color: gradientColors[0].withValues(alpha: isSelected ? 0.5 : 0.25), - blurRadius: isSelected ? 12 : 6, - offset: Offset(0, isSelected ? 4 : 2), - ), - ], + boxShadow: [ + BoxShadow( + color: gradientColors[0].withValues( + alpha: isSelected ? 0.5 : 0.25, + ), + blurRadius: isSelected ? 12 : 6, + offset: Offset(0, isSelected ? 4 : 2), + ), + ], border: isSelected - ? Border.all( - color: Colors.white.withValues(alpha: 0.8), - width: 2, - ) + ? Border.all(color: Colors.white.withValues(alpha: 0.8), width: 2) : Border.all( color: Colors.white.withValues(alpha: 0.15), width: 1, @@ -474,4 +504,4 @@ class _AccountBalanceWidgetState extends State { final formatter = NumberFormat('#,##0.00', 'en_US'); return '\$${formatter.format(balance)}'; } -} \ No newline at end of file +} diff --git a/lib/screens/groups/controllers/batch_controller.dart b/lib/screens/groups/controllers/batch_controller.dart index 4a7e05f..b05fcb9 100644 --- a/lib/screens/groups/controllers/batch_controller.dart +++ b/lib/screens/groups/controllers/batch_controller.dart @@ -36,6 +36,7 @@ class BatchScreenModel { int batchItemsTotalElements = 0; String batchItemsSearchQuery = ''; String? batchItemsStatusFilter; + String? batchItemsRecipientAccount; bool batchItemsIsLoadingMore = false; // Provider products cache: providerId -> list of products @@ -196,7 +197,7 @@ class BatchController extends ChangeNotifier { } final raw = await http.get( - '/public/group-batches?${buildQueryParameters(params)}', + '/group-batches?${buildQueryParameters(params)}', ); final response = _unwrap(raw); final pageableModel = PageableModel.fromJson( @@ -268,8 +269,7 @@ class BatchController extends ChangeNotifier { String batchId, { int page = 0, int size = 20, - String? search, - String? status, + Map? filters, }) async { if (page == 0) { model.isLoading = true; @@ -287,16 +287,12 @@ class BatchController extends ChangeNotifier { 'size': size.toString(), 'sort': 'createdAt,desc', }; - if (search != null && search.isNotEmpty) { - params['recipientName'] = search; - params['recipientPhone'] = search; - } - if (status != null && status.isNotEmpty) { - params['status'] = status; + if (filters != null) { + params.addAll(filters); } final raw = await http.get( - '/public/group-batches/items?${buildQueryParameters(params)}', + '/group-batches/items?${buildQueryParameters(params)}', ); final response = _unwrap(raw); final pageableModel = PageableModel.fromJson( @@ -337,10 +333,20 @@ class BatchController extends ChangeNotifier { String batchId, { String? search, String? status, + String? account, }) async { model.batchItemsSearchQuery = search ?? ''; model.batchItemsStatusFilter = status; - await listBatchItems(batchId, page: 0, search: search, status: status); + model.batchItemsRecipientAccount = account; + await listBatchItems( + batchId, + page: 0, + filters: _buildBatchItemFilters( + search: search, + status: status, + account: account, + ), + ); } Future loadNextBatchItemsPage(String batchId) async { @@ -351,22 +357,44 @@ class BatchController extends ChangeNotifier { await listBatchItems( batchId, page: model.batchItemsCurrentPage + 1, - search: model.batchItemsSearchQuery.isNotEmpty - ? model.batchItemsSearchQuery - : null, - status: model.batchItemsStatusFilter, + filters: _buildBatchItemFilters( + search: model.batchItemsSearchQuery.isNotEmpty + ? model.batchItemsSearchQuery + : null, + status: model.batchItemsStatusFilter, + account: model.batchItemsRecipientAccount, + ), ); } + /// Builds the filters map for batch items queries from individual params. + Map? _buildBatchItemFilters({ + String? search, + String? status, + String? account, + }) { + final filters = {}; + if (search != null && search.isNotEmpty) { + filters['recipientName'] = search; + } + if (status != null && status.isNotEmpty) { + filters['status'] = status; + } + if (account != null && account.isNotEmpty) { + filters['recipientAccount'] = account; + } + return filters.isEmpty ? null : filters; + } + Future> getBatch( String batchId, - String userId, + String workspaceId, ) async { model.isActing = true; if (!_disposed) notifyListeners(); try { final raw = await http.get( - '/public/group-batches/$batchId?userId=$userId', + '/group-batches/$batchId?workspaceId=$workspaceId', ); final response = _unwrap(raw); final batch = GroupBatch.fromJson(response as Map); @@ -386,7 +414,7 @@ class BatchController extends ChangeNotifier { model.isActing = true; if (!_disposed) notifyListeners(); try { - final raw = await http.post('/public/group-batches', req.toJson()); + final raw = await http.post('/group-batches', req.toJson()); final response = _unwrap(raw); final batch = GroupBatch.fromJson(response as Map); model.currentBatch = batch; @@ -409,7 +437,7 @@ class BatchController extends ChangeNotifier { if (!_disposed) notifyListeners(); try { final raw = await http.put( - '/public/group-batches/${request.id}', + '/group-batches/${request.id}', request.toJson(), ); final response = _unwrap(raw); @@ -440,7 +468,7 @@ class BatchController extends ChangeNotifier { if (!_disposed) notifyListeners(); try { final raw = await http.put( - '/public/group-batches/$batchId/items', + '/group-batches/$batchId/items', updateList.toJson(), ); final response = _unwrap(raw); @@ -463,16 +491,21 @@ class BatchController extends ChangeNotifier { Future> confirmBatch( String batchId, - String userId, + String workspaceId, + String authType, ) async { model.isActing = true; if (!_disposed) notifyListeners(); try { final key = 'batch-$batchId-confirm-${DateTime.now().millisecondsSinceEpoch}'; - final body = BatchActionRequest(userId: userId, idempotencyKey: key); + final body = BatchActionRequest( + workspaceId: workspaceId, + authType: authType, + idempotencyKey: key, + ); final raw = await http.post( - '/public/group-batches/$batchId/confirm', + '/group-batches/$batchId/confirm', body.toJson(), ); final response = _unwrap(raw); @@ -491,16 +524,19 @@ class BatchController extends ChangeNotifier { Future> requestBatch( String batchId, - String userId, + String workspaceId, ) async { model.isActing = true; if (!_disposed) notifyListeners(); try { final key = 'batch-$batchId-request-${DateTime.now().millisecondsSinceEpoch}'; - final body = BatchActionRequest(userId: userId, idempotencyKey: key); + final body = BatchActionRequest( + workspaceId: workspaceId, + idempotencyKey: key, + ); final raw = await http.post( - '/public/group-batches/$batchId/request', + '/group-batches/$batchId/request', body.toJson(), ); final response = _unwrap(raw); @@ -519,13 +555,13 @@ class BatchController extends ChangeNotifier { Future>> downloadBatchReport( String batchId, - String userId, + String workspaceId, ) async { model.isActing = true; if (!_disposed) notifyListeners(); try { final raw = await http.get( - '/public/group-batches/$batchId/report?userId=$userId', + '/group-batches/$batchId/report?workspaceId=$workspaceId', ); final response = _unwrap(raw); model.isActing = false; @@ -542,16 +578,19 @@ class BatchController extends ChangeNotifier { Future> pollBatch( String batchId, - String userId, + String workspaceId, ) async { model.isActing = true; if (!_disposed) notifyListeners(); try { final key = 'batch-$batchId-poll-${DateTime.now().millisecondsSinceEpoch}'; - final body = BatchActionRequest(userId: userId, idempotencyKey: key); + final body = BatchActionRequest( + workspaceId: workspaceId, + idempotencyKey: key, + ); final raw = await http.post( - '/public/group-batches/$batchId/poll', + '/group-batches/$batchId/poll', body.toJson(), ); final response = _unwrap(raw); @@ -578,10 +617,10 @@ class BatchController extends ChangeNotifier { Future deleteBatch({ required String batchId, - required String userId, + required String workspaceId, }) async { try { - await http.delete('/public/group-batches/$batchId?userId=$userId'); + await http.delete('/group-batches/$batchId?workspaceId=$workspaceId'); model.batches.removeWhere((b) => b.id == batchId); model.selectedBatchIds.remove(batchId); model.currentBatch = null; @@ -595,7 +634,7 @@ class BatchController extends ChangeNotifier { Future deleteSelectedBatches({ required String groupId, - required String userId, + required String workspaceId, }) async { final idsToDelete = List.from(model.selectedBatchIds); model.selectMode = false; @@ -603,7 +642,7 @@ class BatchController extends ChangeNotifier { if (!_disposed) notifyListeners(); for (final id in idsToDelete) { - await deleteBatch(batchId: id, userId: userId); + await deleteBatch(batchId: id, workspaceId: workspaceId); } // Refresh after deletion diff --git a/lib/screens/groups/controllers/group_controller.dart b/lib/screens/groups/controllers/group_controller.dart index 89b4499..2238378 100644 --- a/lib/screens/groups/controllers/group_controller.dart +++ b/lib/screens/groups/controllers/group_controller.dart @@ -53,7 +53,7 @@ class GroupController extends ChangeNotifier { } Future> listGroups({ - required String userId, + required String workspaceId, int page = 0, int size = 20, String? name, @@ -69,7 +69,7 @@ class GroupController extends ChangeNotifier { try { final params = { - 'userId': userId, + 'workspaceId': workspaceId, 'page': page.toString(), 'size': size.toString(), }; @@ -78,7 +78,7 @@ class GroupController extends ChangeNotifier { } final raw = await http.get( - '/public/recipient-groups?${buildQueryParameters(params)}', + '/recipient-groups?${buildQueryParameters(params)}', ); final response = _unwrap(raw); final pageableModel = PageableModel.fromJson( @@ -115,17 +115,17 @@ class GroupController extends ChangeNotifier { } } - Future searchGroups({required String userId, String? name}) async { + Future searchGroups({required String workspaceId, String? name}) async { model.searchQuery = name ?? ''; - await listGroups(userId: userId, page: 0, name: name); + await listGroups(workspaceId: workspaceId, page: 0, name: name); } - Future loadNextPage({required String userId}) async { + Future loadNextPage({required String workspaceId}) async { if (model.isLoadingMore || model.currentPage + 1 >= model.totalPages) { return; } await listGroups( - userId: userId, + workspaceId: workspaceId, page: model.currentPage + 1, name: model.searchQuery.isNotEmpty ? model.searchQuery : null, ); @@ -138,7 +138,7 @@ class GroupController extends ChangeNotifier { if (!_disposed) notifyListeners(); try { final raw = await http.get( - '/public/recipient-groups/members?recipientGroupId=$groupId', + '/recipient-groups/members?recipientGroupId=$groupId', ); final response = _unwrap(raw); final List content; @@ -172,7 +172,7 @@ class GroupController extends ChangeNotifier { model.isLoading = true; if (!_disposed) notifyListeners(); try { - final raw = await http.post('/public/recipient-groups', req.toJson()); + final raw = await http.post('/recipient-groups', req.toJson()); final response = _unwrap(raw); final group = RecipientGroup.fromJson(response as Map); model.groups = [group, ...model.groups]; @@ -190,11 +190,11 @@ class GroupController extends ChangeNotifier { Future deleteGroup({ required String groupId, - required String userId, + required String workspaceId, }) async { try { await http.delete( - '/public/recipient-groups/delete/$groupId?userId=$userId', + '/recipient-groups/delete/$groupId?workspaceId=$workspaceId', ); model.groups.removeWhere((g) => g.id == groupId); model.selectedGroupIds.remove(groupId); @@ -226,11 +226,11 @@ class GroupController extends ChangeNotifier { Future removeMember({ required String groupId, required String recipientId, - required String userId, + required String workspaceId, }) async { try { await http.delete( - '/public/recipient-groups/$groupId/members/$recipientId?userId=$userId', + '/recipient-groups/$groupId/members/$recipientId?workspaceId=$workspaceId', ); model.members.removeWhere((m) => m.id == recipientId); if (!_disposed) notifyListeners(); @@ -241,19 +241,20 @@ class GroupController extends ChangeNotifier { } } - Future deleteSelectedGroups({required String userId}) async { + Future deleteSelectedGroups({required String workspaceId}) async { final idsToDelete = List.from(model.selectedGroupIds); model.selectMode = false; model.selectedGroupIds.clear(); if (!_disposed) notifyListeners(); for (final id in idsToDelete) { - await deleteGroup(groupId: id, userId: userId); + await deleteGroup(groupId: id, workspaceId: workspaceId); } } Future> createGroupFromFile({ required String groupName, + required String workspaceId, required String userId, String? description, required String currency, @@ -266,6 +267,7 @@ class GroupController extends ChangeNotifier { try { final fields = { 'groupName': groupName, + 'workspaceId': workspaceId, 'userId': userId, 'currency': currency, }; @@ -274,7 +276,7 @@ class GroupController extends ChangeNotifier { } final raw = await http.multipartPost( - '/public/recipient-groups/create-from-file', + '/recipient-groups/create-from-file', fields, 'file', fileBytes, diff --git a/lib/screens/groups/models/batch_item_update_model.dart b/lib/screens/groups/models/batch_item_update_model.dart index d32a5c3..46c2697 100644 --- a/lib/screens/groups/models/batch_item_update_model.dart +++ b/lib/screens/groups/models/batch_item_update_model.dart @@ -35,9 +35,9 @@ class GroupBatchItemUpdateRequest { @JsonSerializable(explicitToJson: true) class GroupBatchItemUpdateList { final List? items; - final String? userId; + final String? workspaceId; - const GroupBatchItemUpdateList({this.items, this.userId}); + const GroupBatchItemUpdateList({this.items, this.workspaceId}); factory GroupBatchItemUpdateList.fromJson(Map json) => _$GroupBatchItemUpdateListFromJson(json); diff --git a/lib/screens/groups/models/batch_item_update_model.g.dart b/lib/screens/groups/models/batch_item_update_model.g.dart index 25deb7e..1936d7c 100644 --- a/lib/screens/groups/models/batch_item_update_model.g.dart +++ b/lib/screens/groups/models/batch_item_update_model.g.dart @@ -42,12 +42,12 @@ GroupBatchItemUpdateList _$GroupBatchItemUpdateListFromJson( (e) => GroupBatchItemUpdateRequest.fromJson(e as Map), ) .toList(), - userId: json['userId'] as String?, + workspaceId: json['workspaceId'] as String?, ); Map _$GroupBatchItemUpdateListToJson( GroupBatchItemUpdateList instance, ) => { 'items': instance.items?.map((e) => e.toJson()).toList(), - 'userId': instance.userId, + 'workspaceId': instance.workspaceId, }; diff --git a/lib/screens/groups/models/group_batch_model.dart b/lib/screens/groups/models/group_batch_model.dart index 062d369..4b26be0 100644 --- a/lib/screens/groups/models/group_batch_model.dart +++ b/lib/screens/groups/models/group_batch_model.dart @@ -7,6 +7,7 @@ class GroupBatch { final String? id; final String? recipientGroupId; final String? userId; + final String? workspaceId; final String? currency; final String? description; final double? value; @@ -24,6 +25,7 @@ class GroupBatch { this.id, this.recipientGroupId, this.userId, + this.workspaceId, this.currency, this.description, this.value, @@ -88,6 +90,7 @@ class GroupBatchItem { class CreateBatchRequest { final String recipientGroupId; final String userId; + final String workspaceId; final String currency; final String description; final double amount; @@ -98,6 +101,7 @@ class CreateBatchRequest { const CreateBatchRequest({ required this.recipientGroupId, required this.userId, + required this.workspaceId, required this.currency, required this.description, required this.amount, @@ -114,12 +118,14 @@ class CreateBatchRequest { @JsonSerializable() class BatchActionRequest { - final String userId; + final String workspaceId; final String idempotencyKey; + final String? authType; const BatchActionRequest({ - required this.userId, + required this.workspaceId, required this.idempotencyKey, + this.authType, }); factory BatchActionRequest.fromJson(Map json) => @@ -131,14 +137,14 @@ class BatchActionRequest { @JsonSerializable(explicitToJson: true) class GroupBatchUpdateRequest { String? id; - String? userId; + String? workspaceId; String? paymentProcessorLabel; String? paymentProcessorName; String? paymentProcessorImage; GroupBatchUpdateRequest({ this.id, - this.userId, + this.workspaceId, this.paymentProcessorLabel, this.paymentProcessorName, this.paymentProcessorImage, diff --git a/lib/screens/groups/models/group_batch_model.g.dart b/lib/screens/groups/models/group_batch_model.g.dart index bb6cd31..29b8bed 100644 --- a/lib/screens/groups/models/group_batch_model.g.dart +++ b/lib/screens/groups/models/group_batch_model.g.dart @@ -10,6 +10,7 @@ GroupBatch _$GroupBatchFromJson(Map json) => GroupBatch( id: json['id'] as String?, recipientGroupId: json['recipientGroupId'] as String?, userId: json['userId'] as String?, + workspaceId: json['workspaceId'] as String?, currency: json['currency'] as String?, description: json['description'] as String?, value: (json['value'] as num?)?.toDouble(), @@ -29,6 +30,7 @@ Map _$GroupBatchToJson(GroupBatch instance) => 'id': instance.id, 'recipientGroupId': instance.recipientGroupId, 'userId': instance.userId, + 'workspaceId': instance.workspaceId, 'currency': instance.currency, 'description': instance.description, 'value': instance.value, @@ -83,6 +85,7 @@ CreateBatchRequest _$CreateBatchRequestFromJson(Map json) => CreateBatchRequest( recipientGroupId: json['recipientGroupId'] as String, userId: json['userId'] as String, + workspaceId: json['workspaceId'] as String, currency: json['currency'] as String, description: json['description'] as String, amount: (json['amount'] as num).toDouble(), @@ -95,6 +98,7 @@ Map _$CreateBatchRequestToJson(CreateBatchRequest instance) => { 'recipientGroupId': instance.recipientGroupId, 'userId': instance.userId, + 'workspaceId': instance.workspaceId, 'currency': instance.currency, 'description': instance.description, 'amount': instance.amount, @@ -105,21 +109,23 @@ Map _$CreateBatchRequestToJson(CreateBatchRequest instance) => BatchActionRequest _$BatchActionRequestFromJson(Map json) => BatchActionRequest( - userId: json['userId'] as String, + workspaceId: json['workspaceId'] as String, idempotencyKey: json['idempotencyKey'] as String, + authType: json['authType'] as String?, ); Map _$BatchActionRequestToJson(BatchActionRequest instance) => { - 'userId': instance.userId, + 'workspaceId': instance.workspaceId, 'idempotencyKey': instance.idempotencyKey, + 'authType': instance.authType, }; GroupBatchUpdateRequest _$GroupBatchUpdateRequestFromJson( Map json, ) => GroupBatchUpdateRequest( id: json['id'] as String?, - userId: json['userId'] as String?, + workspaceId: json['workspaceId'] as String?, paymentProcessorLabel: json['paymentProcessorLabel'] as String?, paymentProcessorName: json['paymentProcessorName'] as String?, paymentProcessorImage: json['paymentProcessorImage'] as String?, @@ -129,7 +135,7 @@ Map _$GroupBatchUpdateRequestToJson( GroupBatchUpdateRequest instance, ) => { 'id': instance.id, - 'userId': instance.userId, + 'workspaceId': instance.workspaceId, 'paymentProcessorLabel': instance.paymentProcessorLabel, 'paymentProcessorName': instance.paymentProcessorName, 'paymentProcessorImage': instance.paymentProcessorImage, diff --git a/lib/screens/groups/models/recipient_group_model.dart b/lib/screens/groups/models/recipient_group_model.dart index 4408ac6..033905d 100644 --- a/lib/screens/groups/models/recipient_group_model.dart +++ b/lib/screens/groups/models/recipient_group_model.dart @@ -9,6 +9,7 @@ class RecipientGroup { final String name; final String? description; final String? userId; + final String? workspaceId; final String? createdAt; const RecipientGroup({ @@ -16,6 +17,7 @@ class RecipientGroup { required this.name, this.description, this.userId, + this.workspaceId, this.createdAt, }); @@ -56,12 +58,14 @@ class CreateGroupRequest { final String name; final String? description; final String userId; + final String workspaceId; final List recipients; const CreateGroupRequest({ required this.name, this.description, required this.userId, + required this.workspaceId, required this.recipients, }); diff --git a/lib/screens/groups/models/recipient_group_model.g.dart b/lib/screens/groups/models/recipient_group_model.g.dart index 6d691d5..3668b75 100644 --- a/lib/screens/groups/models/recipient_group_model.g.dart +++ b/lib/screens/groups/models/recipient_group_model.g.dart @@ -12,6 +12,7 @@ RecipientGroup _$RecipientGroupFromJson(Map json) => name: json['name'] as String, description: json['description'] as String?, userId: json['userId'] as String?, + workspaceId: json['workspaceId'] as String?, createdAt: json['createdAt'] as String?, ); @@ -21,6 +22,7 @@ Map _$RecipientGroupToJson(RecipientGroup instance) => 'name': instance.name, 'description': instance.description, 'userId': instance.userId, + 'workspaceId': instance.workspaceId, 'createdAt': instance.createdAt, }; @@ -53,6 +55,7 @@ CreateGroupRequest _$CreateGroupRequestFromJson(Map json) => name: json['name'] as String, description: json['description'] as String?, userId: json['userId'] as String, + workspaceId: json['workspaceId'] as String, recipients: (json['recipients'] as List) .map((e) => Recipient.fromJson(e as Map)) .toList(), @@ -63,5 +66,6 @@ Map _$CreateGroupRequestToJson(CreateGroupRequest instance) => 'name': instance.name, 'description': instance.description, 'userId': instance.userId, + 'workspaceId': instance.workspaceId, 'recipients': instance.recipients.map((e) => e.toJson()).toList(), }; diff --git a/lib/screens/groups/screens/batch_create_screen.dart b/lib/screens/groups/screens/batch_create_screen.dart index d80093f..c1fc1d9 100644 --- a/lib/screens/groups/screens/batch_create_screen.dart +++ b/lib/screens/groups/screens/batch_create_screen.dart @@ -90,10 +90,12 @@ class _BatchCreateScreenState extends State { final prefs = await SharedPreferences.getInstance(); final userId = prefs.getString('userId') ?? ''; + final workspaceId = prefs.getString('workspaceId') ?? ''; final req = CreateBatchRequest( recipientGroupId: widget.group.id ?? '', userId: userId, + workspaceId: workspaceId, currency: _currency, description: _descriptionController.text.trim(), amount: amount, @@ -115,7 +117,7 @@ class _BatchCreateScreenState extends State { batch.id!, selectedProvider, selectedProduct, - userId, + workspaceId, ); } @@ -130,7 +132,7 @@ class _BatchCreateScreenState extends State { String batchId, BillProvider provider, BillProduct? product, - String userId, + String workspaceId, ) async { // Fetch the batch items so we can update them final itemsResult = await batchController.listBatchItems(batchId); @@ -154,7 +156,7 @@ class _BatchCreateScreenState extends State { final updateList = GroupBatchItemUpdateList( items: updateItems, - userId: userId, + workspaceId: workspaceId, ); await batchController.updateBatchItems(batchId, updateList); diff --git a/lib/screens/groups/screens/batch_detail_screen.dart b/lib/screens/groups/screens/batch_detail_screen.dart index 98ab6d8..2b5d942 100644 --- a/lib/screens/groups/screens/batch_detail_screen.dart +++ b/lib/screens/groups/screens/batch_detail_screen.dart @@ -33,12 +33,13 @@ class _BatchDetailScreenState extends State ConfirmController? _confirmController; GroupBatchUpdateRequest? _cachedUpdateReq; bool _showPollButton = false; - String _userId = ''; + String _workspaceId = ''; bool _initialLoading = true; String? _loadError; PaymentProcessor? _selectedProcessor; final TextEditingController _searchController = TextEditingController(); + final TextEditingController _accountController = TextEditingController(); final ScrollController _scrollController = ScrollController(); bool _editMode = false; @@ -85,9 +86,9 @@ class _BatchDetailScreenState extends State Future _init() async { final prefs = await SharedPreferences.getInstance(); - _userId = prefs.getString('userId') ?? ''; + _workspaceId = prefs.getString('workspaceId') ?? ''; - final result = await controller.getBatch(widget.batchId, _userId); + final result = await controller.getBatch(widget.batchId, _workspaceId); if (!mounted) return; if (result.isSuccess) { @@ -117,6 +118,7 @@ class _BatchDetailScreenState extends State @override void dispose() { _searchController.dispose(); + _accountController.dispose(); _scrollController.dispose(); _entryController.dispose(); _confirmController?.dispose(); @@ -136,7 +138,7 @@ class _BatchDetailScreenState extends State // Persist the selected payment processor on the batch before confirming final updateReq = GroupBatchUpdateRequest( id: batch.id, - userId: _userId, + workspaceId: _workspaceId, paymentProcessorLabel: processor.label, paymentProcessorName: processor.name, paymentProcessorImage: processor.image, @@ -152,7 +154,11 @@ class _BatchDetailScreenState extends State _cachedUpdateReq = updateReq; // Step 2: Confirm the batch - final result = await controller.confirmBatch(batch.id!, _userId); + final result = await controller.confirmBatch( + batch.id!, + _workspaceId, + _selectedProcessor!.authType, + ); if (!mounted) return; if (!result.isSuccess) { _showError(result.error ?? 'Confirm failed.'); @@ -431,7 +437,7 @@ class _BatchDetailScreenState extends State final batch = _batch; if (batch == null) return; - final result = await controller.requestBatch(batch.id!, _userId); + final result = await controller.requestBatch(batch.id!, _workspaceId); if (!mounted) return; if (!result.isSuccess) { @@ -484,7 +490,7 @@ class _BatchDetailScreenState extends State } } } else { - final pollResult = await controller.pollBatch(batch.id!, _userId); + final pollResult = await controller.pollBatch(batch.id!, _workspaceId); if (!mounted) return; if (pollResult.isSuccess) { setState(() => _showPollButton = false); @@ -672,7 +678,7 @@ class _BatchDetailScreenState extends State Future _onPoll() async { final batch = _batch; if (batch == null) return; - final result = await controller.pollBatch(batch.id!, _userId); + final result = await controller.pollBatch(batch.id!, _workspaceId); if (!mounted) return; if (result.isSuccess) { setState(() => _showPollButton = false); @@ -685,7 +691,10 @@ class _BatchDetailScreenState extends State Future _onDownloadReport() async { final batch = _batch; if (batch == null) return; - final result = await controller.downloadBatchReport(batch.id!, _userId); + final result = await controller.downloadBatchReport( + batch.id!, + _workspaceId, + ); if (!mounted) return; if (result.isSuccess) { final fileUrl = result.data?['fileUrl'] as String?; @@ -1435,7 +1444,10 @@ class _BatchDetailScreenState extends State if (items.isEmpty) return false; - final updateList = GroupBatchItemUpdateList(items: items, userId: _userId); + final updateList = GroupBatchItemUpdateList( + items: items, + workspaceId: _workspaceId, + ); final result = await controller.updateBatchItems(batch.id!, updateList); if (!mounted) return false; @@ -1492,6 +1504,8 @@ class _BatchDetailScreenState extends State onPressed: () { setSheetState(() { controller.model.batchItemsStatusFilter = null; + controller.model.batchItemsRecipientAccount = null; + _accountController.clear(); }); }, child: const Text('Clear All'), @@ -1517,14 +1531,18 @@ class _BatchDetailScreenState extends State items: const [ DropdownMenuItem(value: null, child: Text('All')), DropdownMenuItem( - value: 'PENDING', - child: Text('Pending'), + value: 'CONFIRMED', + child: Text('CONFIRMED'), ), DropdownMenuItem( - value: 'SUCCESS', - child: Text('Success'), + value: 'INTEGRATED', + child: Text('INTEGRATED'), + ), + DropdownMenuItem(value: 'FAILED', child: Text('FAILED')), + DropdownMenuItem( + value: 'SKIPPED', + child: Text('SKIPPED'), ), - DropdownMenuItem(value: 'FAILED', child: Text('Failed')), ], onChanged: (v) { setSheetState( @@ -1532,6 +1550,24 @@ class _BatchDetailScreenState extends State ); }, ), + const SizedBox(height: 16), + TextField( + controller: _accountController, + decoration: InputDecoration( + labelText: 'Account Number', + hintText: 'Filter by account number', + filled: true, + fillColor: Colors.grey.shade100, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: BorderSide.none, + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 14, + ), + ), + ), const SizedBox(height: 24), SizedBox( width: double.infinity, @@ -1546,6 +1582,9 @@ class _BatchDetailScreenState extends State ? _searchController.text : null, status: controller.model.batchItemsStatusFilter, + account: _accountController.text.isNotEmpty + ? _accountController.text + : null, ); } }, @@ -2021,7 +2060,9 @@ class _BatchDetailScreenState extends State // Action Bar — search, filter, edit, refresh // ────────────────────────────────────────────────────────────── Widget _buildActionBar(ThemeData theme, bool isDark) { - final hasFilters = controller.model.batchItemsStatusFilter != null; + final hasFilters = + controller.model.batchItemsStatusFilter != null || + controller.model.batchItemsRecipientAccount != null; return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -2033,7 +2074,7 @@ class _BatchDetailScreenState extends State child: TextFormField( controller: _searchController, decoration: InputDecoration( - hintText: 'Search by name or phone', + hintText: 'Search by recipient name', prefixIcon: const Icon(Icons.search, size: 20), suffixIcon: _searchController.text.isNotEmpty ? IconButton( @@ -2046,6 +2087,9 @@ class _BatchDetailScreenState extends State batchId, status: controller.model.batchItemsStatusFilter, + account: controller + .model + .batchItemsRecipientAccount, ); } }, @@ -2075,6 +2119,7 @@ class _BatchDetailScreenState extends State batchId, search: value.isNotEmpty ? value : null, status: controller.model.batchItemsStatusFilter, + account: controller.model.batchItemsRecipientAccount, ); } }, @@ -2147,6 +2192,26 @@ class _BatchDetailScreenState extends State search: _searchController.text.isNotEmpty ? _searchController.text : null, + account: controller.model.batchItemsRecipientAccount, + ); + } + }, + theme, + ), + if (controller.model.batchItemsRecipientAccount != null) + _activeFilterChip( + 'Account: ${controller.model.batchItemsRecipientAccount}', + () { + controller.model.batchItemsRecipientAccount = null; + _accountController.clear(); + final batchId = _batch?.id ?? ''; + if (batchId.isNotEmpty) { + controller.searchBatchItems( + batchId, + search: _searchController.text.isNotEmpty + ? _searchController.text + : null, + status: controller.model.batchItemsStatusFilter, ); } }, diff --git a/lib/screens/groups/screens/create_group_from_file_screen.dart b/lib/screens/groups/screens/create_group_from_file_screen.dart index d4ebe60..17e0f50 100644 --- a/lib/screens/groups/screens/create_group_from_file_screen.dart +++ b/lib/screens/groups/screens/create_group_from_file_screen.dart @@ -65,10 +65,12 @@ class _CreateGroupFromFileScreenState extends State { } final prefs = await SharedPreferences.getInstance(); + final workspaceId = prefs.getString('workspaceId') ?? ''; final userId = prefs.getString('userId') ?? ''; final result = await controller.createGroupFromFile( groupName: _groupNameController.text.trim(), + workspaceId: workspaceId, userId: userId, description: _descriptionController.text.trim().isEmpty ? null diff --git a/lib/screens/groups/screens/group_create_screen.dart b/lib/screens/groups/screens/group_create_screen.dart index bfe0904..a51c3b8 100644 --- a/lib/screens/groups/screens/group_create_screen.dart +++ b/lib/screens/groups/screens/group_create_screen.dart @@ -82,6 +82,7 @@ class _GroupCreateScreenState extends State { final prefs = await SharedPreferences.getInstance(); final userId = prefs.getString('userId') ?? ''; + final workspaceId = prefs.getString('workspaceId') ?? ''; final req = CreateGroupRequest( name: _nameController.text.trim(), @@ -89,6 +90,7 @@ class _GroupCreateScreenState extends State { ? null : _descriptionController.text.trim(), userId: userId, + workspaceId: workspaceId, recipients: recipients, ); diff --git a/lib/screens/groups/screens/group_detail_screen.dart b/lib/screens/groups/screens/group_detail_screen.dart index 1a84eb3..58f1e06 100644 --- a/lib/screens/groups/screens/group_detail_screen.dart +++ b/lib/screens/groups/screens/group_detail_screen.dart @@ -552,7 +552,7 @@ class _GroupDetailScreenState extends State await groupController.removeMember( groupId: widget.group.id ?? '', recipientId: member.id ?? '', - userId: widget.group.userId ?? '', + workspaceId: widget.group.workspaceId ?? '', ); } } @@ -675,7 +675,7 @@ class _GroupDetailScreenState extends State } void _onDeleteSelected() async { - final userId = widget.group.userId ?? ''; + final workspaceId = widget.group.workspaceId ?? ''; final count = batchController.model.selectedBatchIds.length; final confirmed = await showDialog( @@ -700,7 +700,7 @@ class _GroupDetailScreenState extends State if (confirmed == true) { await batchController.deleteSelectedBatches( groupId: widget.group.id ?? '', - userId: userId, + workspaceId: workspaceId, ); } } diff --git a/lib/screens/groups/screens/groups_screen.dart b/lib/screens/groups/screens/groups_screen.dart index b55fc86..d5c4a76 100644 --- a/lib/screens/groups/screens/groups_screen.dart +++ b/lib/screens/groups/screens/groups_screen.dart @@ -30,26 +30,28 @@ class _GroupsScreenState extends State { void _onScroll() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { - controller.loadNextPage(userId: prefs.getString('userId') ?? ''); + controller.loadNextPage( + workspaceId: prefs.getString('workspaceId') ?? '', + ); } } Future _loadData() async { prefs = await SharedPreferences.getInstance(); - final userId = prefs.getString('userId') ?? ''; - await controller.listGroups(userId: userId); + final workspaceId = prefs.getString('workspaceId') ?? ''; + await controller.listGroups(workspaceId: workspaceId); } void _onSearchChanged(String value) { - final userId = prefs.getString('userId') ?? ''; + final workspaceId = prefs.getString('workspaceId') ?? ''; controller.searchGroups( - userId: userId, + workspaceId: workspaceId, name: value.isNotEmpty ? value : null, ); } void _onDeleteSelected() async { - final userId = prefs.getString('userId') ?? ''; + final workspaceId = prefs.getString('workspaceId') ?? ''; final count = controller.model.selectedGroupIds.length; final confirmed = await showDialog( @@ -72,7 +74,7 @@ class _GroupsScreenState extends State { ); if (confirmed == true) { - await controller.deleteSelectedGroups(userId: userId); + await controller.deleteSelectedGroups(workspaceId: workspaceId); } } diff --git a/lib/screens/history/history_controller.dart b/lib/screens/history/history_controller.dart index 1a3af84..307b89f 100644 --- a/lib/screens/history/history_controller.dart +++ b/lib/screens/history/history_controller.dart @@ -66,7 +66,7 @@ class HistoryController extends ChangeNotifier { } _safeNotify(); - params['partyType'] = 'INDIVIDUAL'; + params['partyType'] = 'INDIVIDUAL,GROUP'; try { Map response = await http.get( @@ -107,7 +107,7 @@ class HistoryController extends ChangeNotifier { } Future searchTransactions( - String userId, { + String workspaceId, { String? search, String? status, String? type, @@ -116,7 +116,7 @@ class HistoryController extends ChangeNotifier { model.statusFilter = status; model.typeFilter = type; final params = { - 'userId': userId, + 'workspaceId': workspaceId, 'page': '0', 'size': '20', 'sort': 'createdAt,desc', @@ -138,12 +138,12 @@ class HistoryController extends ChangeNotifier { await getTransactions(params); } - Future loadNextPage(String userId) async { + Future loadNextPage(String workspaceId) async { if (model.isLoadingMore || model.currentPage + 1 >= model.totalPages) { return; } final params = { - 'userId': userId, + 'workspaceId': workspaceId, 'page': (model.currentPage + 1).toString(), 'size': '20', 'sort': 'createdAt,desc', diff --git a/lib/screens/history/history_screen.dart b/lib/screens/history/history_screen.dart index fd49756..dd20bdc 100644 --- a/lib/screens/history/history_screen.dart +++ b/lib/screens/history/history_screen.dart @@ -64,16 +64,16 @@ class _HistoryScreenState extends State Future setupData() async { prefs = await SharedPreferences.getInstance(); - final userId = prefs.getString("userId") ?? ''; - await controller.searchTransactions(userId); + final workspaceId = prefs.getString("workspaceId") ?? ''; + await controller.searchTransactions(workspaceId); } void _onScroll() { if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 200) { - final userId = prefs.getString("userId") ?? ''; - if (userId.isNotEmpty) { - controller.loadNextPage(userId); + final workspaceId = prefs.getString("workspaceId") ?? ''; + if (workspaceId.isNotEmpty) { + controller.loadNextPage(workspaceId); } } } @@ -227,9 +227,10 @@ class _HistoryScreenState extends State child: ElevatedButton( onPressed: () { Navigator.of(ctx).pop(); - final userId = prefs.getString("userId") ?? ''; + final workspaceId = + prefs.getString("workspaceId") ?? ''; controller.searchTransactions( - userId, + workspaceId, search: _searchController.text.isNotEmpty ? _searchController.text : null, @@ -327,9 +328,10 @@ class _HistoryScreenState extends State icon: const Icon(Icons.clear), onPressed: () { _searchController.clear(); - final userId = prefs.getString("userId") ?? ''; + final workspaceId = + prefs.getString("workspaceId") ?? ''; controller.searchTransactions( - userId, + workspaceId, status: controller.model.statusFilter, type: controller.model.typeFilter, ); @@ -351,9 +353,9 @@ class _HistoryScreenState extends State ), textInputAction: TextInputAction.search, onFieldSubmitted: (value) { - final userId = prefs.getString("userId") ?? ''; + final workspaceId = prefs.getString("workspaceId") ?? ''; controller.searchTransactions( - userId, + workspaceId, search: value.isNotEmpty ? value : null, status: controller.model.statusFilter, type: controller.model.typeFilter, @@ -496,9 +498,9 @@ class _HistoryScreenState extends State 'Status: ${controller.model.statusFilter}', () { controller.model.statusFilter = null; - final userId = prefs.getString("userId") ?? ''; + final workspaceId = prefs.getString("workspaceId") ?? ''; controller.searchTransactions( - userId, + workspaceId, search: _searchController.text.isNotEmpty ? _searchController.text : null, @@ -509,9 +511,9 @@ class _HistoryScreenState extends State if (controller.model.typeFilter != null) _activeFilterChip('Type: ${controller.model.typeFilter}', () { controller.model.typeFilter = null; - final userId = prefs.getString("userId") ?? ''; + final workspaceId = prefs.getString("workspaceId") ?? ''; controller.searchTransactions( - userId, + workspaceId, search: _searchController.text.isNotEmpty ? _searchController.text : null, diff --git a/lib/screens/home/home_controller.dart b/lib/screens/home/home_controller.dart index a099d2c..97a8678 100644 --- a/lib/screens/home/home_controller.dart +++ b/lib/screens/home/home_controller.dart @@ -148,14 +148,14 @@ class HomeController extends ChangeNotifier { } Future>>> getTransactions( - String userId, + String workspaceId, ) async { model.isLoading = true; if (!_disposed) notifyListeners(); try { Map response = await http.get( - '/public/transaction?sort=createdAt,desc&size=3&page=0&partyType=INDIVIDUAL&userId=$userId', + '/public/transaction?sort=createdAt,desc&size=3&page=0&partyType=INDIVIDUAL,GROUP&workspaceId=$workspaceId', ); PageableModel pageableModel = PageableModel.fromJson(response); diff --git a/lib/screens/home/home_screen.dart b/lib/screens/home/home_screen.dart index 8002a5f..9999aa1 100644 --- a/lib/screens/home/home_screen.dart +++ b/lib/screens/home/home_screen.dart @@ -127,11 +127,11 @@ class _HomeScreenState extends State with TickerProviderStateMixin { }); } - if (prefs.getString("userId") == null) { - prefs.setString("userId", Uuid().v4()); + if (prefs.getString("workspaceId") == null) { + prefs.setString("workspaceId", Uuid().v4()); } - await homeController.getTransactions(prefs.getString("userId")!); + await homeController.getTransactions(prefs.getString("workspaceId")!); } void _onCurrencySelected(String currency) { @@ -155,8 +155,8 @@ class _HomeScreenState extends State with TickerProviderStateMixin { await homeController.getProviders(_selectedCurrency); prefs = await SharedPreferences.getInstance(); - if (prefs.getString("userId") != null) { - await homeController.getTransactions(prefs.getString("userId")!); + if (prefs.getString("workspaceId") != null) { + await homeController.getTransactions(prefs.getString("workspaceId")!); } } @@ -165,11 +165,11 @@ class _HomeScreenState extends State with TickerProviderStateMixin { if (location.startsWith('/home')) { prefs = await SharedPreferences.getInstance(); - if (prefs.getString("userId") == null) { - prefs.setString("userId", Uuid().v4()); + if (prefs.getString("workspaceId") == null) { + prefs.setString("workspaceId", Uuid().v4()); } - await homeController.getTransactions(prefs.getString("userId")!); + await homeController.getTransactions(prefs.getString("workspaceId")!); } } diff --git a/lib/screens/onboarding/landing/landing_screen.dart b/lib/screens/onboarding/landing/landing_screen.dart index 0736489..3a37ee3 100644 --- a/lib/screens/onboarding/landing/landing_screen.dart +++ b/lib/screens/onboarding/landing/landing_screen.dart @@ -102,10 +102,10 @@ class _LandingScreenState extends State { ), const SizedBox(height: 8), Center( - child: TextButton( - onPressed: () { - context.go('/'); - }, + child: TextButton( + onPressed: () { + context.go('/workspace'); + }, child: Text( 'Continue as guest?', style: TextStyle( diff --git a/lib/screens/onboarding/login/login_screen.dart b/lib/screens/onboarding/login/login_screen.dart index 93679ee..5e152bc 100644 --- a/lib/screens/onboarding/login/login_screen.dart +++ b/lib/screens/onboarding/login/login_screen.dart @@ -78,7 +78,7 @@ class _LoginScreenState extends State { if (!mounted) return; if (response.isSuccess) { - context.go('/'); + context.go('/workspace'); } } diff --git a/lib/screens/pay/pay_controller.dart b/lib/screens/pay/pay_controller.dart index 9cb5d82..f3723b1 100644 --- a/lib/screens/pay/pay_controller.dart +++ b/lib/screens/pay/pay_controller.dart @@ -99,6 +99,7 @@ class PayController extends ChangeNotifier { paymentProcessorImage: model.selectedPaymentProcessor?.image ?? '', providerImage: model.selectedProvider?.image ?? '', providerLabel: model.selectedProvider?.label ?? '', + workspaceId: prefs.getString("workspaceId") ?? '', userId: prefs.getString("userId") ?? '', creditName: transactionController.model.formData.creditName, creditEmail: transactionController.model.formData.creditEmail, diff --git a/lib/screens/pay/pay_controller.freezed.dart b/lib/screens/pay/pay_controller.freezed.dart index 7ebb80c..aab1fce 100644 --- a/lib/screens/pay/pay_controller.freezed.dart +++ b/lib/screens/pay/pay_controller.freezed.dart @@ -13,7 +13,7 @@ part of 'pay_controller.dart'; T _$identity(T value) => value; /// @nodoc -mixin _$PaymentProcessor { +mixin _$PaymentProcessor implements DiagnosticableTreeMixin { String get name; String get description; String get image; String get accountFieldName; String get accountFieldLabel; String get label; String get authType; /// Create a copy of PaymentProcessor @@ -25,6 +25,12 @@ $PaymentProcessorCopyWith get copyWith => _$PaymentProcessorCo /// Serializes this PaymentProcessor to a JSON map. Map toJson(); +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'PaymentProcessor')) + ..add(DiagnosticsProperty('name', name))..add(DiagnosticsProperty('description', description))..add(DiagnosticsProperty('image', image))..add(DiagnosticsProperty('accountFieldName', accountFieldName))..add(DiagnosticsProperty('accountFieldLabel', accountFieldLabel))..add(DiagnosticsProperty('label', label))..add(DiagnosticsProperty('authType', authType)); +} @override bool operator ==(Object other) { @@ -36,7 +42,7 @@ bool operator ==(Object other) { int get hashCode => Object.hash(runtimeType,name,description,image,accountFieldName,accountFieldLabel,label,authType); @override -String toString() { +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { return 'PaymentProcessor(name: $name, description: $description, image: $image, accountFieldName: $accountFieldName, accountFieldLabel: $accountFieldLabel, label: $label, authType: $authType)'; } @@ -214,7 +220,7 @@ return $default(_that.name,_that.description,_that.image,_that.accountFieldName, /// @nodoc @JsonSerializable() -class _PaymentProcessor implements PaymentProcessor { +class _PaymentProcessor with DiagnosticableTreeMixin implements PaymentProcessor { const _PaymentProcessor({required this.name, required this.description, required this.image, required this.accountFieldName, required this.accountFieldLabel, required this.label, required this.authType}); factory _PaymentProcessor.fromJson(Map json) => _$PaymentProcessorFromJson(json); @@ -236,6 +242,12 @@ _$PaymentProcessorCopyWith<_PaymentProcessor> get copyWith => __$PaymentProcesso Map toJson() { return _$PaymentProcessorToJson(this, ); } +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'PaymentProcessor')) + ..add(DiagnosticsProperty('name', name))..add(DiagnosticsProperty('description', description))..add(DiagnosticsProperty('image', image))..add(DiagnosticsProperty('accountFieldName', accountFieldName))..add(DiagnosticsProperty('accountFieldLabel', accountFieldLabel))..add(DiagnosticsProperty('label', label))..add(DiagnosticsProperty('authType', authType)); +} @override bool operator ==(Object other) { @@ -247,7 +259,7 @@ bool operator ==(Object other) { int get hashCode => Object.hash(runtimeType,name,description,image,accountFieldName,accountFieldLabel,label,authType); @override -String toString() { +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { return 'PaymentProcessor(name: $name, description: $description, image: $image, accountFieldName: $accountFieldName, accountFieldLabel: $accountFieldLabel, label: $label, authType: $authType)'; } @@ -294,7 +306,7 @@ as String, /// @nodoc -mixin _$BillProduct { +mixin _$BillProduct implements DiagnosticableTreeMixin { String get uid; String get name; String get description; String get displayName; double get defaultAmount; bool get requiresAmount; /// Create a copy of BillProduct @@ -306,6 +318,12 @@ $BillProductCopyWith get copyWith => _$BillProductCopyWithImpl toJson(); +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'BillProduct')) + ..add(DiagnosticsProperty('uid', uid))..add(DiagnosticsProperty('name', name))..add(DiagnosticsProperty('description', description))..add(DiagnosticsProperty('displayName', displayName))..add(DiagnosticsProperty('defaultAmount', defaultAmount))..add(DiagnosticsProperty('requiresAmount', requiresAmount)); +} @override bool operator ==(Object other) { @@ -317,7 +335,7 @@ bool operator ==(Object other) { int get hashCode => Object.hash(runtimeType,uid,name,description,displayName,defaultAmount,requiresAmount); @override -String toString() { +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { return 'BillProduct(uid: $uid, name: $name, description: $description, displayName: $displayName, defaultAmount: $defaultAmount, requiresAmount: $requiresAmount)'; } @@ -494,7 +512,7 @@ return $default(_that.uid,_that.name,_that.description,_that.displayName,_that.d /// @nodoc @JsonSerializable() -class _BillProduct implements BillProduct { +class _BillProduct with DiagnosticableTreeMixin implements BillProduct { const _BillProduct({required this.uid, required this.name, required this.description, required this.displayName, required this.defaultAmount, required this.requiresAmount}); factory _BillProduct.fromJson(Map json) => _$BillProductFromJson(json); @@ -515,6 +533,12 @@ _$BillProductCopyWith<_BillProduct> get copyWith => __$BillProductCopyWithImpl<_ Map toJson() { return _$BillProductToJson(this, ); } +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'BillProduct')) + ..add(DiagnosticsProperty('uid', uid))..add(DiagnosticsProperty('name', name))..add(DiagnosticsProperty('description', description))..add(DiagnosticsProperty('displayName', displayName))..add(DiagnosticsProperty('defaultAmount', defaultAmount))..add(DiagnosticsProperty('requiresAmount', requiresAmount)); +} @override bool operator ==(Object other) { @@ -526,7 +550,7 @@ bool operator ==(Object other) { int get hashCode => Object.hash(runtimeType,uid,name,description,displayName,defaultAmount,requiresAmount); @override -String toString() { +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { return 'BillProduct(uid: $uid, name: $name, description: $description, displayName: $displayName, defaultAmount: $defaultAmount, requiresAmount: $requiresAmount)'; } diff --git a/lib/screens/receipt/receipt_controller.dart b/lib/screens/receipt/receipt_controller.dart index 74f8e5e..f33008f 100644 --- a/lib/screens/receipt/receipt_controller.dart +++ b/lib/screens/receipt/receipt_controller.dart @@ -125,8 +125,12 @@ class ReceiptController extends ChangeNotifier { Future> getReceiptData(String id) async { setLoading(true); + + final prefs = await SharedPreferences.getInstance(); try { - Map response = await http.get('/public/transaction/$id'); + Map response = await http.get( + '/public/transaction/$id?workspaceId=${prefs.getString("workspaceId")}', + ); final transaction = TransactionModel.fromJson(response); model.transaction = transaction; setLoading(false); diff --git a/lib/screens/recipient/recipients_controller.dart b/lib/screens/recipient/recipients_controller.dart index 461c371..21640f1 100644 --- a/lib/screens/recipient/recipients_controller.dart +++ b/lib/screens/recipient/recipients_controller.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:qpay/http/http.dart'; @@ -6,6 +7,8 @@ import 'package:qpay/screens/home/home_controller.dart' as hc; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:qpay/screens/transaction_controller.dart'; +import '../../models/pageable_model.dart'; + part 'recipients_controller.freezed.dart'; part 'recipients_controller.g.dart'; @@ -56,18 +59,25 @@ class RecipientsController extends ChangeNotifier { notifyListeners(); try { - List response = await http.get( - '/public/recipients/search?${buildQueryParameters(params ?? {})}', + final response = await http.get( + '/public/recipients?${buildQueryParameters(params ?? {})}', ); + final page = PageableModel.fromJson(response as Map); model.recipients.clear(); - model.recipients.addAll(response.map((e) => Recipient.fromJson(e))); + model.recipients.addAll( + page.content.map((e) => Recipient.fromJson(e)).toList(), + ); model.isLoading = false; notifyListeners(); - return ApiResponse.success(List.from(model.recipients)); - } catch (e) { + return ApiResponse.success(model.recipients); + } catch (e, s) { + if (kDebugMode) { + logger.e(e); + logger.e(s); + } + model.errorMessage = e.toString(); model.recipients = []; - logger.e(e); model.isLoading = false; notifyListeners(); return ApiResponse.failure( diff --git a/lib/screens/recipient/recipients_controller.freezed.dart b/lib/screens/recipient/recipients_controller.freezed.dart index 3066b54..6c90613 100644 --- a/lib/screens/recipient/recipients_controller.freezed.dart +++ b/lib/screens/recipient/recipients_controller.freezed.dart @@ -13,7 +13,7 @@ part of 'recipients_controller.dart'; T _$identity(T value) => value; /// @nodoc -mixin _$Recipient { +mixin _$Recipient implements DiagnosticableTreeMixin { @JsonKey(includeIfNull: false) String? get uid;@JsonKey(includeIfNull: false) String? get name;@JsonKey(includeIfNull: false) String? get email;@JsonKey(includeIfNull: false) String? get phoneNumber;@JsonKey(includeIfNull: false) String? get address;@JsonKey(includeIfNull: false) String? get account;@JsonKey(includeIfNull: false) String? get initials;@JsonKey(includeIfNull: false) String? get latestProviderLabel; /// Create a copy of Recipient @@ -25,6 +25,12 @@ $RecipientCopyWith get copyWith => _$RecipientCopyWithImpl /// Serializes this Recipient to a JSON map. Map toJson(); +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'Recipient')) + ..add(DiagnosticsProperty('uid', uid))..add(DiagnosticsProperty('name', name))..add(DiagnosticsProperty('email', email))..add(DiagnosticsProperty('phoneNumber', phoneNumber))..add(DiagnosticsProperty('address', address))..add(DiagnosticsProperty('account', account))..add(DiagnosticsProperty('initials', initials))..add(DiagnosticsProperty('latestProviderLabel', latestProviderLabel)); +} @override bool operator ==(Object other) { @@ -36,7 +42,7 @@ bool operator ==(Object other) { int get hashCode => Object.hash(runtimeType,uid,name,email,phoneNumber,address,account,initials,latestProviderLabel); @override -String toString() { +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { return 'Recipient(uid: $uid, name: $name, email: $email, phoneNumber: $phoneNumber, address: $address, account: $account, initials: $initials, latestProviderLabel: $latestProviderLabel)'; } @@ -215,7 +221,7 @@ return $default(_that.uid,_that.name,_that.email,_that.phoneNumber,_that.address /// @nodoc @JsonSerializable() -class _Recipient implements Recipient { +class _Recipient with DiagnosticableTreeMixin implements Recipient { const _Recipient({@JsonKey(includeIfNull: false) this.uid, @JsonKey(includeIfNull: false) this.name, @JsonKey(includeIfNull: false) this.email, @JsonKey(includeIfNull: false) this.phoneNumber, @JsonKey(includeIfNull: false) this.address, @JsonKey(includeIfNull: false) this.account, @JsonKey(includeIfNull: false) this.initials, @JsonKey(includeIfNull: false) this.latestProviderLabel}); factory _Recipient.fromJson(Map json) => _$RecipientFromJson(json); @@ -238,6 +244,12 @@ _$RecipientCopyWith<_Recipient> get copyWith => __$RecipientCopyWithImpl<_Recipi Map toJson() { return _$RecipientToJson(this, ); } +@override +void debugFillProperties(DiagnosticPropertiesBuilder properties) { + properties + ..add(DiagnosticsProperty('type', 'Recipient')) + ..add(DiagnosticsProperty('uid', uid))..add(DiagnosticsProperty('name', name))..add(DiagnosticsProperty('email', email))..add(DiagnosticsProperty('phoneNumber', phoneNumber))..add(DiagnosticsProperty('address', address))..add(DiagnosticsProperty('account', account))..add(DiagnosticsProperty('initials', initials))..add(DiagnosticsProperty('latestProviderLabel', latestProviderLabel)); +} @override bool operator ==(Object other) { @@ -249,7 +261,7 @@ bool operator ==(Object other) { int get hashCode => Object.hash(runtimeType,uid,name,email,phoneNumber,address,account,initials,latestProviderLabel); @override -String toString() { +String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { return 'Recipient(uid: $uid, name: $name, email: $email, phoneNumber: $phoneNumber, address: $address, account: $account, initials: $initials, latestProviderLabel: $latestProviderLabel)'; } diff --git a/lib/screens/recipient/recipients_screen.dart b/lib/screens/recipient/recipients_screen.dart index b0d1e78..30d00ef 100644 --- a/lib/screens/recipient/recipients_screen.dart +++ b/lib/screens/recipient/recipients_screen.dart @@ -66,7 +66,7 @@ class _RecipientsScreenState extends State Future setupData() async { prefs = await SharedPreferences.getInstance(); - _queryParams['userId'] = prefs.getString("userId")!; + _queryParams['workspaceId'] = prefs.getString("workspaceId")!; controller.getRecipients(_queryParams); } @@ -268,7 +268,7 @@ class _RecipientsScreenState extends State _queryParams['email'] = _accountController.text; _queryParams['phoneNumber'] = _accountController.text; _queryParams['name'] = _accountController.text; - _queryParams['userId'] = prefs.getString("userId")!; + _queryParams['workspaceId'] = prefs.getString("workspaceId")!; controller.getRecipients(_queryParams); }); diff --git a/lib/screens/transaction_controller.dart b/lib/screens/transaction_controller.dart index dd79d93..28e509d 100644 --- a/lib/screens/transaction_controller.dart +++ b/lib/screens/transaction_controller.dart @@ -39,6 +39,7 @@ class TransactionModel { paymentProcessorImage: '', providerImage: '', userId: '', + workspaceId: '', providerLabel: '', region: '', ); @@ -69,6 +70,7 @@ abstract class FormData with _$FormData { required String providerImage, required String providerLabel, required String userId, + required String workspaceId, String? debitPhone, String? debitAccount, String? productUid, @@ -150,6 +152,7 @@ class TransactionController extends ChangeNotifier { paymentProcessorImage: '', providerImage: '', userId: '', + workspaceId: '', providerLabel: '', region: '', ); diff --git a/lib/screens/transaction_controller.freezed.dart b/lib/screens/transaction_controller.freezed.dart index 447b78a..ee8291b 100644 --- a/lib/screens/transaction_controller.freezed.dart +++ b/lib/screens/transaction_controller.freezed.dart @@ -16,7 +16,7 @@ T _$identity(T value) => value; mixin _$FormData { String? get id; String get type;// CONFIRM, REQUEST, REVERSE - String get billClientId; String get debitRef; String get debitCurrency; String get amount; String get creditAccount; String? get creditPhone; String? get creditName; String? get creditEmail; String get billName; String? get errorMessage; String get status; String get paymentProcessorLabel; String get paymentProcessorName; String get paymentProcessorImage; String get providerImage; String get providerLabel; String get userId; String? get debitPhone; String? get debitAccount; String? get productUid; String? get billProductName; String? get trace; String? get authType; String? get charge; String? get gatewayCharge; String? get tax; String? get totalAmount; String? get region; + String get billClientId; String get debitRef; String get debitCurrency; String get amount; String get creditAccount; String? get creditPhone; String? get creditName; String? get creditEmail; String get billName; String? get errorMessage; String get status; String get paymentProcessorLabel; String get paymentProcessorName; String get paymentProcessorImage; String get providerImage; String get providerLabel; String get userId; String get workspaceId; String? get debitPhone; String? get debitAccount; String? get productUid; String? get billProductName; String? get trace; String? get authType; String? get charge; String? get gatewayCharge; String? get tax; String? get totalAmount; String? get region; /// Create a copy of FormData /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -29,16 +29,16 @@ $FormDataCopyWith get copyWith => _$FormDataCopyWithImpl(thi @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is FormData&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.billProductName, billProductName) || other.billProductName == billProductName)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)&&(identical(other.region, region) || other.region == region)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is FormData&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.billProductName, billProductName) || other.billProductName == billProductName)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)&&(identical(other.region, region) || other.region == region)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hashAll([runtimeType,id,type,billClientId,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,debitPhone,debitAccount,productUid,billProductName,trace,authType,charge,gatewayCharge,tax,totalAmount,region]); +int get hashCode => Object.hashAll([runtimeType,id,type,billClientId,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,workspaceId,debitPhone,debitAccount,productUid,billProductName,trace,authType,charge,gatewayCharge,tax,totalAmount,region]); @override String toString() { - return 'FormData(id: $id, type: $type, billClientId: $billClientId, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, billProductName: $billProductName, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount, region: $region)'; + return 'FormData(id: $id, type: $type, billClientId: $billClientId, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, workspaceId: $workspaceId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, billProductName: $billProductName, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount, region: $region)'; } @@ -49,7 +49,7 @@ abstract mixin class $FormDataCopyWith<$Res> { factory $FormDataCopyWith(FormData value, $Res Function(FormData) _then) = _$FormDataCopyWithImpl; @useResult $Res call({ - String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region + String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String workspaceId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region }); @@ -66,7 +66,7 @@ class _$FormDataCopyWithImpl<$Res> /// Create a copy of FormData /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? type = null,Object? billClientId = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? billProductName = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,Object? region = freezed,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? type = null,Object? billClientId = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? workspaceId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? billProductName = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,Object? region = freezed,}) { return _then(_self.copyWith( id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable @@ -87,6 +87,7 @@ as String,paymentProcessorImage: null == paymentProcessorImage ? _self.paymentPr as String,providerImage: null == providerImage ? _self.providerImage : providerImage // ignore: cast_nullable_to_non_nullable as String,providerLabel: null == providerLabel ? _self.providerLabel : providerLabel // ignore: cast_nullable_to_non_nullable as String,userId: null == userId ? _self.userId : userId // ignore: cast_nullable_to_non_nullable +as String,workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable as String,debitPhone: freezed == debitPhone ? _self.debitPhone : debitPhone // ignore: cast_nullable_to_non_nullable as String?,debitAccount: freezed == debitAccount ? _self.debitAccount : debitAccount // ignore: cast_nullable_to_non_nullable as String?,productUid: freezed == productUid ? _self.productUid : productUid // ignore: cast_nullable_to_non_nullable @@ -183,10 +184,10 @@ return $default(_that);case _: /// } /// ``` -@optionalTypeArgs TResult maybeWhen(TResult Function( String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(TResult Function( String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String workspaceId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region)? $default,{required TResult orElse(),}) {final _that = this; switch (_that) { case _FormData() when $default != null: -return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debitCurrency,_that.amount,_that.creditAccount,_that.creditPhone,_that.creditName,_that.creditEmail,_that.billName,_that.errorMessage,_that.status,_that.paymentProcessorLabel,_that.paymentProcessorName,_that.paymentProcessorImage,_that.providerImage,_that.providerLabel,_that.userId,_that.debitPhone,_that.debitAccount,_that.productUid,_that.billProductName,_that.trace,_that.authType,_that.charge,_that.gatewayCharge,_that.tax,_that.totalAmount,_that.region);case _: +return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debitCurrency,_that.amount,_that.creditAccount,_that.creditPhone,_that.creditName,_that.creditEmail,_that.billName,_that.errorMessage,_that.status,_that.paymentProcessorLabel,_that.paymentProcessorName,_that.paymentProcessorImage,_that.providerImage,_that.providerLabel,_that.userId,_that.workspaceId,_that.debitPhone,_that.debitAccount,_that.productUid,_that.billProductName,_that.trace,_that.authType,_that.charge,_that.gatewayCharge,_that.tax,_that.totalAmount,_that.region);case _: return orElse(); } @@ -204,10 +205,10 @@ return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debi /// } /// ``` -@optionalTypeArgs TResult when(TResult Function( String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region) $default,) {final _that = this; +@optionalTypeArgs TResult when(TResult Function( String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String workspaceId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region) $default,) {final _that = this; switch (_that) { case _FormData(): -return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debitCurrency,_that.amount,_that.creditAccount,_that.creditPhone,_that.creditName,_that.creditEmail,_that.billName,_that.errorMessage,_that.status,_that.paymentProcessorLabel,_that.paymentProcessorName,_that.paymentProcessorImage,_that.providerImage,_that.providerLabel,_that.userId,_that.debitPhone,_that.debitAccount,_that.productUid,_that.billProductName,_that.trace,_that.authType,_that.charge,_that.gatewayCharge,_that.tax,_that.totalAmount,_that.region);case _: +return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debitCurrency,_that.amount,_that.creditAccount,_that.creditPhone,_that.creditName,_that.creditEmail,_that.billName,_that.errorMessage,_that.status,_that.paymentProcessorLabel,_that.paymentProcessorName,_that.paymentProcessorImage,_that.providerImage,_that.providerLabel,_that.userId,_that.workspaceId,_that.debitPhone,_that.debitAccount,_that.productUid,_that.billProductName,_that.trace,_that.authType,_that.charge,_that.gatewayCharge,_that.tax,_that.totalAmount,_that.region);case _: throw StateError('Unexpected subclass'); } @@ -224,10 +225,10 @@ return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debi /// } /// ``` -@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(TResult? Function( String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String workspaceId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region)? $default,) {final _that = this; switch (_that) { case _FormData() when $default != null: -return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debitCurrency,_that.amount,_that.creditAccount,_that.creditPhone,_that.creditName,_that.creditEmail,_that.billName,_that.errorMessage,_that.status,_that.paymentProcessorLabel,_that.paymentProcessorName,_that.paymentProcessorImage,_that.providerImage,_that.providerLabel,_that.userId,_that.debitPhone,_that.debitAccount,_that.productUid,_that.billProductName,_that.trace,_that.authType,_that.charge,_that.gatewayCharge,_that.tax,_that.totalAmount,_that.region);case _: +return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debitCurrency,_that.amount,_that.creditAccount,_that.creditPhone,_that.creditName,_that.creditEmail,_that.billName,_that.errorMessage,_that.status,_that.paymentProcessorLabel,_that.paymentProcessorName,_that.paymentProcessorImage,_that.providerImage,_that.providerLabel,_that.userId,_that.workspaceId,_that.debitPhone,_that.debitAccount,_that.productUid,_that.billProductName,_that.trace,_that.authType,_that.charge,_that.gatewayCharge,_that.tax,_that.totalAmount,_that.region);case _: return null; } @@ -239,7 +240,7 @@ return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debi @JsonSerializable() class _FormData implements FormData { - const _FormData({this.id, required this.type, required this.billClientId, required this.debitRef, required this.debitCurrency, required this.amount, required this.creditAccount, required this.creditPhone, required this.creditName, required this.creditEmail, required this.billName, required this.errorMessage, required this.status, required this.paymentProcessorLabel, required this.paymentProcessorName, required this.paymentProcessorImage, required this.providerImage, required this.providerLabel, required this.userId, this.debitPhone, this.debitAccount, this.productUid, this.billProductName, this.trace, this.authType, this.charge, this.gatewayCharge, this.tax, this.totalAmount, this.region}); + const _FormData({this.id, required this.type, required this.billClientId, required this.debitRef, required this.debitCurrency, required this.amount, required this.creditAccount, required this.creditPhone, required this.creditName, required this.creditEmail, required this.billName, required this.errorMessage, required this.status, required this.paymentProcessorLabel, required this.paymentProcessorName, required this.paymentProcessorImage, required this.providerImage, required this.providerLabel, required this.userId, required this.workspaceId, this.debitPhone, this.debitAccount, this.productUid, this.billProductName, this.trace, this.authType, this.charge, this.gatewayCharge, this.tax, this.totalAmount, this.region}); factory _FormData.fromJson(Map json) => _$FormDataFromJson(json); @override final String? id; @@ -262,6 +263,7 @@ class _FormData implements FormData { @override final String providerImage; @override final String providerLabel; @override final String userId; +@override final String workspaceId; @override final String? debitPhone; @override final String? debitAccount; @override final String? productUid; @@ -287,16 +289,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _FormData&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.billProductName, billProductName) || other.billProductName == billProductName)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)&&(identical(other.region, region) || other.region == region)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _FormData&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.billProductName, billProductName) || other.billProductName == billProductName)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)&&(identical(other.region, region) || other.region == region)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hashAll([runtimeType,id,type,billClientId,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,debitPhone,debitAccount,productUid,billProductName,trace,authType,charge,gatewayCharge,tax,totalAmount,region]); +int get hashCode => Object.hashAll([runtimeType,id,type,billClientId,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,workspaceId,debitPhone,debitAccount,productUid,billProductName,trace,authType,charge,gatewayCharge,tax,totalAmount,region]); @override String toString() { - return 'FormData(id: $id, type: $type, billClientId: $billClientId, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, billProductName: $billProductName, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount, region: $region)'; + return 'FormData(id: $id, type: $type, billClientId: $billClientId, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, workspaceId: $workspaceId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, billProductName: $billProductName, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount, region: $region)'; } @@ -307,7 +309,7 @@ abstract mixin class _$FormDataCopyWith<$Res> implements $FormDataCopyWith<$Res> factory _$FormDataCopyWith(_FormData value, $Res Function(_FormData) _then) = __$FormDataCopyWithImpl; @override @useResult $Res call({ - String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region + String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String workspaceId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region }); @@ -324,7 +326,7 @@ class __$FormDataCopyWithImpl<$Res> /// Create a copy of FormData /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? type = null,Object? billClientId = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? billProductName = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,Object? region = freezed,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? type = null,Object? billClientId = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? workspaceId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? billProductName = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,Object? region = freezed,}) { return _then(_FormData( id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable @@ -345,6 +347,7 @@ as String,paymentProcessorImage: null == paymentProcessorImage ? _self.paymentPr as String,providerImage: null == providerImage ? _self.providerImage : providerImage // ignore: cast_nullable_to_non_nullable as String,providerLabel: null == providerLabel ? _self.providerLabel : providerLabel // ignore: cast_nullable_to_non_nullable as String,userId: null == userId ? _self.userId : userId // ignore: cast_nullable_to_non_nullable +as String,workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable as String,debitPhone: freezed == debitPhone ? _self.debitPhone : debitPhone // ignore: cast_nullable_to_non_nullable as String?,debitAccount: freezed == debitAccount ? _self.debitAccount : debitAccount // ignore: cast_nullable_to_non_nullable as String?,productUid: freezed == productUid ? _self.productUid : productUid // ignore: cast_nullable_to_non_nullable diff --git a/lib/screens/transaction_controller.g.dart b/lib/screens/transaction_controller.g.dart index f04bc2b..13405e9 100644 --- a/lib/screens/transaction_controller.g.dart +++ b/lib/screens/transaction_controller.g.dart @@ -26,6 +26,7 @@ _FormData _$FormDataFromJson(Map json) => _FormData( providerImage: json['providerImage'] as String, providerLabel: json['providerLabel'] as String, userId: json['userId'] as String, + workspaceId: json['workspaceId'] as String, debitPhone: json['debitPhone'] as String?, debitAccount: json['debitAccount'] as String?, productUid: json['productUid'] as String?, @@ -59,6 +60,7 @@ Map _$FormDataToJson(_FormData instance) => { 'providerImage': instance.providerImage, 'providerLabel': instance.providerLabel, 'userId': instance.userId, + 'workspaceId': instance.workspaceId, 'debitPhone': instance.debitPhone, 'debitAccount': instance.debitAccount, 'productUid': instance.productUid, diff --git a/lib/screens/workspaces/workspace_model.dart b/lib/screens/workspaces/workspace_model.dart new file mode 100644 index 0000000..dcdc0d5 --- /dev/null +++ b/lib/screens/workspaces/workspace_model.dart @@ -0,0 +1,47 @@ +class WorkspaceModel { + final String id; + final String createdAt; + final String updatedAt; + final bool deleted; + final String name; + final String description; + final String phone; + final String email; + + WorkspaceModel({ + required this.id, + required this.createdAt, + required this.updatedAt, + required this.deleted, + required this.name, + required this.description, + required this.phone, + required this.email, + }); + + factory WorkspaceModel.fromJson(Map json) { + return WorkspaceModel( + id: json['id'] as String, + createdAt: json['createdAt'] as String, + updatedAt: json['updatedAt'] as String, + deleted: json['deleted'] as bool, + name: json['name'] as String, + description: json['description'] as String, + phone: json['phone'] as String, + email: json['email'] as String, + ); + } + + Map toJson() { + return { + 'id': id, + 'createdAt': createdAt, + 'updatedAt': updatedAt, + 'deleted': deleted, + 'name': name, + 'description': description, + 'phone': phone, + 'email': email, + }; + } +} \ No newline at end of file diff --git a/lib/screens/workspaces/workspace_provider.dart b/lib/screens/workspaces/workspace_provider.dart new file mode 100644 index 0000000..a66fb9c --- /dev/null +++ b/lib/screens/workspaces/workspace_provider.dart @@ -0,0 +1,73 @@ +import 'package:flutter/foundation.dart'; +import 'package:qpay/http/http.dart'; +import 'package:qpay/screens/workspaces/workspace_model.dart'; + +/// Describes the current state of the workspace fetch operation. +enum WorkspaceStatus { idle, loading, success, error } + +/// A [ChangeNotifier] that fetches and caches the list of workspaces +/// for the current user from the backend. +/// +/// Usage: +/// ```dart +/// final provider = context.read(); +/// await provider.fetchWorkspaces(userId: '...'); +/// ``` +class WorkspaceProvider extends ChangeNotifier { + final Http _http = Http(); + + WorkspaceStatus _status = WorkspaceStatus.idle; + List _workspaces = []; + String? _error; + + WorkspaceStatus get status => _status; + List get workspaces => _workspaces; + String? get error => _error; + bool get isLoading => _status == WorkspaceStatus.loading; + bool get hasError => _status == WorkspaceStatus.error; + + /// Fetches workspaces for the given [userId]. + /// + /// On success, [workspaces] is updated and [status] becomes + /// [WorkspaceStatus.success]. + /// On failure, [error] is set and [status] becomes + /// [WorkspaceStatus.error]. + Future fetchWorkspaces({required String userId}) async { + _status = WorkspaceStatus.loading; + _error = null; + notifyListeners(); + + try { + final response = await _http.getRaw('/workspaces?userId=$userId'); + + if (response.statusCode == 200) { + final body = response.data as Map; + final content = body['content'] as List; + _workspaces = content + .map((e) => WorkspaceModel.fromJson(e as Map)) + .toList(); + _status = WorkspaceStatus.success; + } else if (response.statusCode == 403) { + _error = + 'Access denied. You do not have permission to view workspaces.'; + _status = WorkspaceStatus.error; + } else { + _error = 'Failed to load workspaces (status ${response.statusCode})'; + _status = WorkspaceStatus.error; + } + } catch (e) { + _error = 'Network error: ${e.toString()}'; + _status = WorkspaceStatus.error; + } + + notifyListeners(); + } + + /// Clears the current workspace list and resets to idle. + void reset() { + _status = WorkspaceStatus.idle; + _workspaces = []; + _error = null; + notifyListeners(); + } +} diff --git a/lib/screens/workspaces/workspace_screen.dart b/lib/screens/workspaces/workspace_screen.dart new file mode 100644 index 0000000..da59928 --- /dev/null +++ b/lib/screens/workspaces/workspace_screen.dart @@ -0,0 +1,346 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; +import 'package:qpay/screens/workspaces/workspace_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:uuid/uuid.dart'; + +import '../../auth_state.dart'; + +/// Full-screen workspace selector displayed immediately after the splash +/// screen. Fetches the user's workspaces from the backend and: +/// +/// * If exactly one workspace exists → auto-navigates to the home screen. +/// * If multiple workspaces exist → shows a picker so the user can choose. +/// * On error (e.g. 403 for guests) → falls through to the home screen. +class WorkspaceScreen extends StatefulWidget { + const WorkspaceScreen({super.key}); + + @override + State createState() => _WorkspaceScreenState(); +} + +class _WorkspaceScreenState extends State + with TickerProviderStateMixin { + late AnimationController _fadeController; + late Animation _fadeAnimation; + late Animation _slideAnimation; + + AuthState authState = AuthState(); + bool _navigated = false; + + @override + void initState() { + super.initState(); + + _fadeController = AnimationController( + duration: const Duration(milliseconds: 400), + vsync: this, + ); + _fadeAnimation = Tween( + begin: 0.0, + end: 1.0, + ).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeOut)); + _slideAnimation = + Tween(begin: const Offset(0, 0.04), end: Offset.zero).animate( + CurvedAnimation(parent: _fadeController, curve: Curves.easeOutCubic), + ); + + // Kick off the workspace fetch after the first frame. + WidgetsBinding.instance.addPostFrameCallback((_) { + _checkAuthAndLoad(); + }); + } + + Future _checkAuthAndLoad() async { + final prefs = await SharedPreferences.getInstance(); + if (prefs.getString("token") != null) { + _loadWorkspaces(); + } else { + // if not logged in then create temp workspace + SharedPreferences.getInstance().then((prefs) { + prefs.setString('workspaceId', const Uuid().v4()); + }); + _navigateToHome(); + } + } + + Future _loadWorkspaces() async { + // Ensure a userId exists in prefs. + final prefs = await SharedPreferences.getInstance(); + if (prefs.getString("userId") == null) { + await prefs.setString("userId", const Uuid().v4()); + } + final userId = prefs.getString("userId")!; + + if (!mounted) return; + + final provider = context.read(); + // Start the fade-in animation. + _fadeController.forward(); + + await provider.fetchWorkspaces(userId: userId); + + if (!mounted) return; + _evaluateNavigation(provider); + } + + void _evaluateNavigation(WorkspaceProvider provider) { + if (_navigated) return; + + final workspaces = provider.workspaces; + + if (workspaces.length == 1) { + // Store the selected workspace ID so downstream screens can + // reference it if needed. + SharedPreferences.getInstance().then((prefs) { + prefs.setString('workspaceId', workspaces.first.id); + }); + _navigateToHome(); + } else if (workspaces.isEmpty) { + // No workspaces or an error occurred – go to home. + _navigateToHome(); + } + // 2+ workspaces: stay on this screen so the user can pick one. + } + + void _onWorkspaceSelected(String workspaceId) async { + if (_navigated) return; + _navigated = true; + + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('workspaceId', workspaceId); + + if (!mounted) return; + _navigateToHome(); + } + + void _navigateToHome() { + if (_navigated) return; + _navigated = true; + context.go('/'); + } + + @override + void dispose() { + _fadeController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return Scaffold( + backgroundColor: const Color(0xFFF8F9FA), + body: Consumer( + builder: (context, provider, _) { + if (provider.isLoading) { + return const Center(child: CircularProgressIndicator()); + } + + // Error or empty: navigate away (handled above, but show + // a brief fallback if navigation hasn't triggered yet). + if (provider.hasError || provider.workspaces.isEmpty) { + return const SizedBox.shrink(); + } + + // Multiple workspaces → show the picker. + final workspaces = provider.workspaces; + + return SafeArea( + child: FadeTransition( + opacity: _fadeAnimation, + child: SlideTransition( + position: _slideAnimation, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Spacer(flex: 2), + // Header + Container( + width: 56, + height: 56, + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues( + alpha: 0.1, + ), + borderRadius: BorderRadius.circular(16), + ), + child: Icon( + Icons.business_rounded, + color: theme.colorScheme.primary, + size: 28, + ), + ), + const SizedBox(height: 24), + Text( + 'Select a workspace', + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w700, + color: Colors.black87, + ), + ), + const SizedBox(height: 8), + Text( + 'Choose the workspace you want to work in.', + style: theme.textTheme.bodyMedium?.copyWith( + color: Colors.grey.shade600, + ), + ), + const SizedBox(height: 32), + // Workspace list + Expanded( + child: ListView.separated( + itemCount: workspaces.length, + separatorBuilder: (_, __) => + const SizedBox(height: 12), + itemBuilder: (context, index) { + final ws = workspaces[index]; + return _WorkspaceCard( + name: ws.name, + description: ws.description, + email: ws.email, + phone: ws.phone, + onTap: () => _onWorkspaceSelected(ws.id), + ); + }, + ), + ), + const Spacer(flex: 1), + ], + ), + ), + ), + ), + ); + }, + ), + ); + } +} + +/// A single workspace card shown in the picker list. +class _WorkspaceCard extends StatelessWidget { + final String name; + final String description; + final String email; + final String phone; + final VoidCallback onTap; + + const _WorkspaceCard({ + required this.name, + required this.description, + required this.email, + required this.phone, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final isDark = theme.brightness == Brightness.dark; + + return Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(16), + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: isDark ? const Color(0xFF1E1E1E) : Colors.white, + borderRadius: BorderRadius.circular(16), + border: Border.all( + color: isDark + ? Colors.white.withValues(alpha: 0.06) + : Colors.grey.shade200, + ), + ), + child: Row( + children: [ + // Avatar placeholder + Container( + width: 48, + height: 48, + decoration: BoxDecoration( + color: theme.colorScheme.primary.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(12), + ), + child: Center( + child: Text( + name.isNotEmpty ? name[0].toUpperCase() : 'W', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w700, + color: theme.colorScheme.primary, + ), + ), + ), + ), + const SizedBox(width: 16), + // Details + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w600, + color: Colors.black87, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + if (description.isNotEmpty) ...[ + const SizedBox(height: 4), + Text( + description, + style: TextStyle( + fontSize: 13, + color: Colors.grey.shade500, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + if (email.isNotEmpty) ...[ + const SizedBox(height: 4), + Row( + children: [ + Icon( + Icons.email_outlined, + size: 14, + color: Colors.grey.shade400, + ), + const SizedBox(width: 6), + Expanded( + child: Text( + email, + style: TextStyle( + fontSize: 12, + color: Colors.grey.shade400, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ], + ), + ), + const SizedBox(width: 8), + Icon(Icons.chevron_right_rounded, color: Colors.grey.shade400), + ], + ), + ), + ), + ); + } +}