completed migrating data scope from user to workspace

This commit is contained in:
2026-06-22 22:10:38 +02:00
parent 62c7f53de0
commit ae2705363a
37 changed files with 918 additions and 221 deletions

View File

@@ -61,17 +61,18 @@ class Http {
/// Issues a POST request without authentication headers and returns /// Issues a POST request without authentication headers and returns
/// the raw [Response] object. /// the raw [Response] object.
Future<Response> postRaw(String url, {dynamic data}) async { Future<Response> postRaw(String url, {dynamic data}) async {
Map<String, String> headers = await getHeaders();
final response = await dio.post( final response = await dio.post(
baseUrl + url, baseUrl + url,
data: data, data: data,
options: Options(headers: {'Content-Type': 'application/json'}), options: Options(headers: headers),
); );
logger.i(response.data.toString()); logger.i(response.data.toString());
return response; return response;
} }
Future<dynamic> post(String url, dynamic data) async { Future<dynamic> post(String url, dynamic data) async {
Map<String, String> headers = {'Content-Type': 'application/json'}; Map<String, String> headers = await getHeaders();
Response response; Response response;
response = await dio.post( response = await dio.post(
@@ -130,10 +131,7 @@ class Http {
) async { ) async {
final formData = FormData.fromMap({ final formData = FormData.fromMap({
...fields, ...fields,
fileFieldName: MultipartFile.fromBytes( fileFieldName: MultipartFile.fromBytes(fileBytes, filename: fileName),
fileBytes,
filename: fileName,
),
}); });
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
@@ -142,9 +140,7 @@ class Http {
final response = await dio.post( final response = await dio.post(
baseUrl + url, baseUrl + url,
data: formData, data: formData,
options: Options(headers: { options: Options(headers: {'Authorization': 'Bearer $token'}),
'Authorization': 'Bearer $token',
}),
); );
logger.i(response.data.toString()); logger.i(response.data.toString());
return response.data; return response.data;

View File

@@ -7,6 +7,7 @@ import 'package:qpay/url_strategy.dart';
import 'package:qpay/screens/transaction_controller.dart'; import 'package:qpay/screens/transaction_controller.dart';
import 'package:qpay/screens/onboarding/onboarding_controller.dart'; import 'package:qpay/screens/onboarding/onboarding_controller.dart';
import 'package:qpay/providers/splash_provider.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:qpay/theme/app_theme.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
@@ -53,6 +54,9 @@ void main() async {
ChangeNotifierProvider<SplashProvider>( ChangeNotifierProvider<SplashProvider>(
create: (_) => SplashProvider(), create: (_) => SplashProvider(),
), ),
ChangeNotifierProvider<WorkspaceProvider>(
create: (_) => WorkspaceProvider(),
),
], ],
child: const MyApp(), child: const MyApp(),
), ),

View File

@@ -29,6 +29,7 @@ import 'package:qpay/screens/profile_screen.dart';
import 'package:qpay/screens/receipt/receipt_screen.dart'; import 'package:qpay/screens/receipt/receipt_screen.dart';
import 'package:qpay/screens/recipient/recipients_screen.dart'; import 'package:qpay/screens/recipient/recipients_screen.dart';
import 'package:qpay/screens/splash_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 /// Tracks whether the splash animation is complete so that
/// GoRouter's redirect can show the splash first, then release /// GoRouter's redirect can show the splash first, then release
@@ -135,9 +136,10 @@ GoRouter buildRouter() {
return authState.requireAuth(path); 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') { if (path == '/splash') {
return splashState.intendedRoute ?? '/'; return '/workspace';
} }
} }
@@ -153,12 +155,13 @@ GoRouter buildRouter() {
ShellRoute( ShellRoute(
builder: (context, state, child) { builder: (context, state, child) {
final location = GoRouterState.of(context).uri.toString(); final location = GoRouterState.of(context).uri.toString();
if (location.startsWith('/onboarding/')) { if (location.startsWith('/onboarding/') || location == '/workspace') {
return child; return child;
} }
return ScaffoldWithNavBar(child: child); return ScaffoldWithNavBar(child: child);
}, },
routes: [ routes: [
GoRoute(path: '/workspace', builder: (context, state) => const WorkspaceScreen()),
GoRoute(path: '/', builder: (context, state) => const HomeScreen()), GoRoute(path: '/', builder: (context, state) => const HomeScreen()),
GoRoute( GoRoute(
path: '/home', path: '/home',
@@ -229,8 +232,7 @@ GoRouter buildRouter() {
), ),
GoRoute( GoRoute(
path: '/groups/create-from-file', path: '/groups/create-from-file',
builder: (context, state) => builder: (context, state) => const CreateGroupFromFileScreen(),
const CreateGroupFromFileScreen(),
), ),
GoRoute( GoRoute(
path: '/groups/:groupId', path: '/groups/:groupId',

View File

@@ -38,14 +38,17 @@ class AccountProvider extends ChangeNotifier {
super.dispose(); super.dispose();
} }
Future<ApiResponse<AccountModel>> fetchAccount(String phoneNumber) async { Future<ApiResponse<AccountModel>> fetchAccount(
String? workspaceId,
String currency,
) async {
_isLoadingAccount = true; _isLoadingAccount = true;
_accountError = null; _accountError = null;
notifyListeners(); notifyListeners();
try { try {
Map<String, dynamic> response = await http.get( Map<String, dynamic> response = await http.get(
'/accounts/phone/$phoneNumber', '/accounts/$workspaceId/balance/$currency',
); );
_account = AccountModel.fromJson(response); _account = AccountModel.fromJson(response);
@@ -69,7 +72,8 @@ class AccountProvider extends ChangeNotifier {
} }
Future<ApiResponse<StatementModel>> fetchStatement({ Future<ApiResponse<StatementModel>> fetchStatement({
required String phoneNumber, required String workspaceId,
required String currency,
required String startDate, required String startDate,
required String endDate, required String endDate,
}) async { }) async {
@@ -79,7 +83,7 @@ class AccountProvider extends ChangeNotifier {
try { try {
Map<String, dynamic> response = await http.get( Map<String, dynamic> response = await http.get(
'/accounts/phone/$phoneNumber/statement' '/accounts/$workspaceId/statement/$currency'
'?startDate=$startDate&endDate=$endDate', '?startDate=$startDate&endDate=$endDate',
); );

View File

@@ -23,7 +23,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
late AccountProvider _accountProvider; late AccountProvider _accountProvider;
bool _isLoggedIn = false; bool _isLoggedIn = false;
String? _phoneNumber; String? _workspaceId;
AccountModel? _usdAccount; AccountModel? _usdAccount;
AccountModel? _zwgAccount; AccountModel? _zwgAccount;
@@ -31,6 +31,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
bool _isLoadingZwG = false; bool _isLoadingZwG = false;
String? _usdError; String? _usdError;
String? _zwgError; String? _zwgError;
bool _showAccountError = false;
@override @override
void initState() { void initState() {
@@ -55,9 +56,9 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
}); });
} }
_phoneNumber = prefs.getString('phone'); _workspaceId = prefs.getString('workspaceId');
if (_phoneNumber == null || _phoneNumber!.isEmpty) { if (_workspaceId == null || _workspaceId!.isEmpty) {
return; return;
} }
@@ -67,9 +68,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
}); });
// Fetch USD balance // Fetch USD balance
final usdResult = await _accountProvider.fetchAccount( final usdResult = await _accountProvider.fetchAccount(_workspaceId, 'USD');
'USD$_phoneNumber',
);
if (mounted) { if (mounted) {
setState(() { setState(() {
_isLoadingUsd = false; _isLoadingUsd = false;
@@ -83,9 +82,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
} }
// Fetch ZWG balance // Fetch ZWG balance
final zwgResult = await _accountProvider.fetchAccount( final zwgResult = await _accountProvider.fetchAccount(_workspaceId, 'ZWG');
'ZWG$_phoneNumber',
);
if (mounted) { if (mounted) {
setState(() { setState(() {
_isLoadingZwG = false; _isLoadingZwG = false;
@@ -104,25 +101,23 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
!zwgResult.isSuccess && !zwgResult.isSuccess &&
_isLoggedIn) { _isLoggedIn) {
setState(() { setState(() {
_isLoggedIn = false; _showAccountError = true;
}); });
} }
} }
Future<void> _refreshBalances() async { Future<void> _refreshBalances() async {
final prefs = await SharedPreferences.getInstance(); 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(() { setState(() {
_isLoadingUsd = true; _isLoadingUsd = true;
_isLoadingZwG = true; _isLoadingZwG = true;
}); });
final usdResult = await _accountProvider.fetchAccount( final usdResult = await _accountProvider.fetchAccount(_workspaceId, 'USD');
'USD$_phoneNumber',
);
if (mounted) { if (mounted) {
setState(() { setState(() {
_isLoadingUsd = false; _isLoadingUsd = false;
@@ -135,9 +130,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
}); });
} }
final zwgResult = await _accountProvider.fetchAccount( final zwgResult = await _accountProvider.fetchAccount(_workspaceId, 'ZWG');
'ZWG$_phoneNumber',
);
if (mounted) { if (mounted) {
setState(() { setState(() {
_isLoadingZwG = false; _isLoadingZwG = false;
@@ -155,7 +148,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
!zwgResult.isSuccess && !zwgResult.isSuccess &&
_isLoggedIn) { _isLoggedIn) {
setState(() { setState(() {
_isLoggedIn = false; _showAccountError = true;
}); });
} }
} }
@@ -169,10 +162,47 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
return _buildLoginPrompt(theme, isDark); return _buildLoginPrompt(theme, isDark);
} }
if (_phoneNumber == null || _phoneNumber!.isEmpty) { if (_workspaceId == null || _workspaceId!.isEmpty) {
return const SizedBox.shrink(); 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( return Padding(
padding: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.only(bottom: 12),
child: Column( child: Column(
@@ -231,9 +261,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
: theme.colorScheme.primary, : theme.colorScheme.primary,
), ),
label: Text( label: Text(
_isLoadingUsd || _isLoadingZwG _isLoadingUsd || _isLoadingZwG ? 'Refreshing...' : 'Refresh',
? 'Refreshing...'
: 'Refresh',
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@@ -301,7 +329,10 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
style: ElevatedButton.styleFrom( style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary, backgroundColor: theme.colorScheme.primary,
foregroundColor: Colors.white, foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
@@ -345,16 +376,15 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
), ),
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
color: gradientColors[0].withValues(alpha: isSelected ? 0.5 : 0.25), color: gradientColors[0].withValues(
alpha: isSelected ? 0.5 : 0.25,
),
blurRadius: isSelected ? 12 : 6, blurRadius: isSelected ? 12 : 6,
offset: Offset(0, isSelected ? 4 : 2), offset: Offset(0, isSelected ? 4 : 2),
), ),
], ],
border: isSelected border: isSelected
? Border.all( ? Border.all(color: Colors.white.withValues(alpha: 0.8), width: 2)
color: Colors.white.withValues(alpha: 0.8),
width: 2,
)
: Border.all( : Border.all(
color: Colors.white.withValues(alpha: 0.15), color: Colors.white.withValues(alpha: 0.15),
width: 1, width: 1,

View File

@@ -36,6 +36,7 @@ class BatchScreenModel {
int batchItemsTotalElements = 0; int batchItemsTotalElements = 0;
String batchItemsSearchQuery = ''; String batchItemsSearchQuery = '';
String? batchItemsStatusFilter; String? batchItemsStatusFilter;
String? batchItemsRecipientAccount;
bool batchItemsIsLoadingMore = false; bool batchItemsIsLoadingMore = false;
// Provider products cache: providerId -> list of products // Provider products cache: providerId -> list of products
@@ -196,7 +197,7 @@ class BatchController extends ChangeNotifier {
} }
final raw = await http.get( final raw = await http.get(
'/public/group-batches?${buildQueryParameters(params)}', '/group-batches?${buildQueryParameters(params)}',
); );
final response = _unwrap(raw); final response = _unwrap(raw);
final pageableModel = PageableModel.fromJson( final pageableModel = PageableModel.fromJson(
@@ -268,8 +269,7 @@ class BatchController extends ChangeNotifier {
String batchId, { String batchId, {
int page = 0, int page = 0,
int size = 20, int size = 20,
String? search, Map<String, String>? filters,
String? status,
}) async { }) async {
if (page == 0) { if (page == 0) {
model.isLoading = true; model.isLoading = true;
@@ -287,16 +287,12 @@ class BatchController extends ChangeNotifier {
'size': size.toString(), 'size': size.toString(),
'sort': 'createdAt,desc', 'sort': 'createdAt,desc',
}; };
if (search != null && search.isNotEmpty) { if (filters != null) {
params['recipientName'] = search; params.addAll(filters);
params['recipientPhone'] = search;
}
if (status != null && status.isNotEmpty) {
params['status'] = status;
} }
final raw = await http.get( final raw = await http.get(
'/public/group-batches/items?${buildQueryParameters(params)}', '/group-batches/items?${buildQueryParameters(params)}',
); );
final response = _unwrap(raw); final response = _unwrap(raw);
final pageableModel = PageableModel.fromJson( final pageableModel = PageableModel.fromJson(
@@ -337,10 +333,20 @@ class BatchController extends ChangeNotifier {
String batchId, { String batchId, {
String? search, String? search,
String? status, String? status,
String? account,
}) async { }) async {
model.batchItemsSearchQuery = search ?? ''; model.batchItemsSearchQuery = search ?? '';
model.batchItemsStatusFilter = status; 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<void> loadNextBatchItemsPage(String batchId) async { Future<void> loadNextBatchItemsPage(String batchId) async {
@@ -351,22 +357,44 @@ class BatchController extends ChangeNotifier {
await listBatchItems( await listBatchItems(
batchId, batchId,
page: model.batchItemsCurrentPage + 1, page: model.batchItemsCurrentPage + 1,
filters: _buildBatchItemFilters(
search: model.batchItemsSearchQuery.isNotEmpty search: model.batchItemsSearchQuery.isNotEmpty
? model.batchItemsSearchQuery ? model.batchItemsSearchQuery
: null, : null,
status: model.batchItemsStatusFilter, status: model.batchItemsStatusFilter,
account: model.batchItemsRecipientAccount,
),
); );
} }
/// Builds the filters map for batch items queries from individual params.
Map<String, String>? _buildBatchItemFilters({
String? search,
String? status,
String? account,
}) {
final filters = <String, String>{};
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<ApiResponse<GroupBatch>> getBatch( Future<ApiResponse<GroupBatch>> getBatch(
String batchId, String batchId,
String userId, String workspaceId,
) async { ) async {
model.isActing = true; model.isActing = true;
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
try { try {
final raw = await http.get( final raw = await http.get(
'/public/group-batches/$batchId?userId=$userId', '/group-batches/$batchId?workspaceId=$workspaceId',
); );
final response = _unwrap(raw); final response = _unwrap(raw);
final batch = GroupBatch.fromJson(response as Map<String, dynamic>); final batch = GroupBatch.fromJson(response as Map<String, dynamic>);
@@ -386,7 +414,7 @@ class BatchController extends ChangeNotifier {
model.isActing = true; model.isActing = true;
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
try { 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 response = _unwrap(raw);
final batch = GroupBatch.fromJson(response as Map<String, dynamic>); final batch = GroupBatch.fromJson(response as Map<String, dynamic>);
model.currentBatch = batch; model.currentBatch = batch;
@@ -409,7 +437,7 @@ class BatchController extends ChangeNotifier {
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
try { try {
final raw = await http.put( final raw = await http.put(
'/public/group-batches/${request.id}', '/group-batches/${request.id}',
request.toJson(), request.toJson(),
); );
final response = _unwrap(raw); final response = _unwrap(raw);
@@ -440,7 +468,7 @@ class BatchController extends ChangeNotifier {
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
try { try {
final raw = await http.put( final raw = await http.put(
'/public/group-batches/$batchId/items', '/group-batches/$batchId/items',
updateList.toJson(), updateList.toJson(),
); );
final response = _unwrap(raw); final response = _unwrap(raw);
@@ -463,16 +491,21 @@ class BatchController extends ChangeNotifier {
Future<ApiResponse<GroupBatch>> confirmBatch( Future<ApiResponse<GroupBatch>> confirmBatch(
String batchId, String batchId,
String userId, String workspaceId,
String authType,
) async { ) async {
model.isActing = true; model.isActing = true;
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
try { try {
final key = final key =
'batch-$batchId-confirm-${DateTime.now().millisecondsSinceEpoch}'; '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( final raw = await http.post(
'/public/group-batches/$batchId/confirm', '/group-batches/$batchId/confirm',
body.toJson(), body.toJson(),
); );
final response = _unwrap(raw); final response = _unwrap(raw);
@@ -491,16 +524,19 @@ class BatchController extends ChangeNotifier {
Future<ApiResponse<TransactionModel>> requestBatch( Future<ApiResponse<TransactionModel>> requestBatch(
String batchId, String batchId,
String userId, String workspaceId,
) async { ) async {
model.isActing = true; model.isActing = true;
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
try { try {
final key = final key =
'batch-$batchId-request-${DateTime.now().millisecondsSinceEpoch}'; '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( final raw = await http.post(
'/public/group-batches/$batchId/request', '/group-batches/$batchId/request',
body.toJson(), body.toJson(),
); );
final response = _unwrap(raw); final response = _unwrap(raw);
@@ -519,13 +555,13 @@ class BatchController extends ChangeNotifier {
Future<ApiResponse<Map<String, dynamic>>> downloadBatchReport( Future<ApiResponse<Map<String, dynamic>>> downloadBatchReport(
String batchId, String batchId,
String userId, String workspaceId,
) async { ) async {
model.isActing = true; model.isActing = true;
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
try { try {
final raw = await http.get( final raw = await http.get(
'/public/group-batches/$batchId/report?userId=$userId', '/group-batches/$batchId/report?workspaceId=$workspaceId',
); );
final response = _unwrap(raw); final response = _unwrap(raw);
model.isActing = false; model.isActing = false;
@@ -542,16 +578,19 @@ class BatchController extends ChangeNotifier {
Future<ApiResponse<TransactionModel>> pollBatch( Future<ApiResponse<TransactionModel>> pollBatch(
String batchId, String batchId,
String userId, String workspaceId,
) async { ) async {
model.isActing = true; model.isActing = true;
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
try { try {
final key = final key =
'batch-$batchId-poll-${DateTime.now().millisecondsSinceEpoch}'; '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( final raw = await http.post(
'/public/group-batches/$batchId/poll', '/group-batches/$batchId/poll',
body.toJson(), body.toJson(),
); );
final response = _unwrap(raw); final response = _unwrap(raw);
@@ -578,10 +617,10 @@ class BatchController extends ChangeNotifier {
Future<void> deleteBatch({ Future<void> deleteBatch({
required String batchId, required String batchId,
required String userId, required String workspaceId,
}) async { }) async {
try { 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.batches.removeWhere((b) => b.id == batchId);
model.selectedBatchIds.remove(batchId); model.selectedBatchIds.remove(batchId);
model.currentBatch = null; model.currentBatch = null;
@@ -595,7 +634,7 @@ class BatchController extends ChangeNotifier {
Future<void> deleteSelectedBatches({ Future<void> deleteSelectedBatches({
required String groupId, required String groupId,
required String userId, required String workspaceId,
}) async { }) async {
final idsToDelete = List<String>.from(model.selectedBatchIds); final idsToDelete = List<String>.from(model.selectedBatchIds);
model.selectMode = false; model.selectMode = false;
@@ -603,7 +642,7 @@ class BatchController extends ChangeNotifier {
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
for (final id in idsToDelete) { for (final id in idsToDelete) {
await deleteBatch(batchId: id, userId: userId); await deleteBatch(batchId: id, workspaceId: workspaceId);
} }
// Refresh after deletion // Refresh after deletion

View File

@@ -53,7 +53,7 @@ class GroupController extends ChangeNotifier {
} }
Future<ApiResponse<PageableModel>> listGroups({ Future<ApiResponse<PageableModel>> listGroups({
required String userId, required String workspaceId,
int page = 0, int page = 0,
int size = 20, int size = 20,
String? name, String? name,
@@ -69,7 +69,7 @@ class GroupController extends ChangeNotifier {
try { try {
final params = <String, String>{ final params = <String, String>{
'userId': userId, 'workspaceId': workspaceId,
'page': page.toString(), 'page': page.toString(),
'size': size.toString(), 'size': size.toString(),
}; };
@@ -78,7 +78,7 @@ class GroupController extends ChangeNotifier {
} }
final raw = await http.get( final raw = await http.get(
'/public/recipient-groups?${buildQueryParameters(params)}', '/recipient-groups?${buildQueryParameters(params)}',
); );
final response = _unwrap(raw); final response = _unwrap(raw);
final pageableModel = PageableModel.fromJson( final pageableModel = PageableModel.fromJson(
@@ -115,17 +115,17 @@ class GroupController extends ChangeNotifier {
} }
} }
Future<void> searchGroups({required String userId, String? name}) async { Future<void> searchGroups({required String workspaceId, String? name}) async {
model.searchQuery = name ?? ''; model.searchQuery = name ?? '';
await listGroups(userId: userId, page: 0, name: name); await listGroups(workspaceId: workspaceId, page: 0, name: name);
} }
Future<void> loadNextPage({required String userId}) async { Future<void> loadNextPage({required String workspaceId}) async {
if (model.isLoadingMore || model.currentPage + 1 >= model.totalPages) { if (model.isLoadingMore || model.currentPage + 1 >= model.totalPages) {
return; return;
} }
await listGroups( await listGroups(
userId: userId, workspaceId: workspaceId,
page: model.currentPage + 1, page: model.currentPage + 1,
name: model.searchQuery.isNotEmpty ? model.searchQuery : null, name: model.searchQuery.isNotEmpty ? model.searchQuery : null,
); );
@@ -138,7 +138,7 @@ class GroupController extends ChangeNotifier {
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
try { try {
final raw = await http.get( final raw = await http.get(
'/public/recipient-groups/members?recipientGroupId=$groupId', '/recipient-groups/members?recipientGroupId=$groupId',
); );
final response = _unwrap(raw); final response = _unwrap(raw);
final List<dynamic> content; final List<dynamic> content;
@@ -172,7 +172,7 @@ class GroupController extends ChangeNotifier {
model.isLoading = true; model.isLoading = true;
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
try { 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 response = _unwrap(raw);
final group = RecipientGroup.fromJson(response as Map<String, dynamic>); final group = RecipientGroup.fromJson(response as Map<String, dynamic>);
model.groups = [group, ...model.groups]; model.groups = [group, ...model.groups];
@@ -190,11 +190,11 @@ class GroupController extends ChangeNotifier {
Future<void> deleteGroup({ Future<void> deleteGroup({
required String groupId, required String groupId,
required String userId, required String workspaceId,
}) async { }) async {
try { try {
await http.delete( await http.delete(
'/public/recipient-groups/delete/$groupId?userId=$userId', '/recipient-groups/delete/$groupId?workspaceId=$workspaceId',
); );
model.groups.removeWhere((g) => g.id == groupId); model.groups.removeWhere((g) => g.id == groupId);
model.selectedGroupIds.remove(groupId); model.selectedGroupIds.remove(groupId);
@@ -226,11 +226,11 @@ class GroupController extends ChangeNotifier {
Future<void> removeMember({ Future<void> removeMember({
required String groupId, required String groupId,
required String recipientId, required String recipientId,
required String userId, required String workspaceId,
}) async { }) async {
try { try {
await http.delete( 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); model.members.removeWhere((m) => m.id == recipientId);
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
@@ -241,19 +241,20 @@ class GroupController extends ChangeNotifier {
} }
} }
Future<void> deleteSelectedGroups({required String userId}) async { Future<void> deleteSelectedGroups({required String workspaceId}) async {
final idsToDelete = List<String>.from(model.selectedGroupIds); final idsToDelete = List<String>.from(model.selectedGroupIds);
model.selectMode = false; model.selectMode = false;
model.selectedGroupIds.clear(); model.selectedGroupIds.clear();
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
for (final id in idsToDelete) { for (final id in idsToDelete) {
await deleteGroup(groupId: id, userId: userId); await deleteGroup(groupId: id, workspaceId: workspaceId);
} }
} }
Future<ApiResponse<CreateGroupFromFileResponse>> createGroupFromFile({ Future<ApiResponse<CreateGroupFromFileResponse>> createGroupFromFile({
required String groupName, required String groupName,
required String workspaceId,
required String userId, required String userId,
String? description, String? description,
required String currency, required String currency,
@@ -266,6 +267,7 @@ class GroupController extends ChangeNotifier {
try { try {
final fields = <String, String>{ final fields = <String, String>{
'groupName': groupName, 'groupName': groupName,
'workspaceId': workspaceId,
'userId': userId, 'userId': userId,
'currency': currency, 'currency': currency,
}; };
@@ -274,7 +276,7 @@ class GroupController extends ChangeNotifier {
} }
final raw = await http.multipartPost( final raw = await http.multipartPost(
'/public/recipient-groups/create-from-file', '/recipient-groups/create-from-file',
fields, fields,
'file', 'file',
fileBytes, fileBytes,

View File

@@ -35,9 +35,9 @@ class GroupBatchItemUpdateRequest {
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class GroupBatchItemUpdateList { class GroupBatchItemUpdateList {
final List<GroupBatchItemUpdateRequest>? items; final List<GroupBatchItemUpdateRequest>? items;
final String? userId; final String? workspaceId;
const GroupBatchItemUpdateList({this.items, this.userId}); const GroupBatchItemUpdateList({this.items, this.workspaceId});
factory GroupBatchItemUpdateList.fromJson(Map<String, dynamic> json) => factory GroupBatchItemUpdateList.fromJson(Map<String, dynamic> json) =>
_$GroupBatchItemUpdateListFromJson(json); _$GroupBatchItemUpdateListFromJson(json);

View File

@@ -42,12 +42,12 @@ GroupBatchItemUpdateList _$GroupBatchItemUpdateListFromJson(
(e) => GroupBatchItemUpdateRequest.fromJson(e as Map<String, dynamic>), (e) => GroupBatchItemUpdateRequest.fromJson(e as Map<String, dynamic>),
) )
.toList(), .toList(),
userId: json['userId'] as String?, workspaceId: json['workspaceId'] as String?,
); );
Map<String, dynamic> _$GroupBatchItemUpdateListToJson( Map<String, dynamic> _$GroupBatchItemUpdateListToJson(
GroupBatchItemUpdateList instance, GroupBatchItemUpdateList instance,
) => <String, dynamic>{ ) => <String, dynamic>{
'items': instance.items?.map((e) => e.toJson()).toList(), 'items': instance.items?.map((e) => e.toJson()).toList(),
'userId': instance.userId, 'workspaceId': instance.workspaceId,
}; };

View File

@@ -7,6 +7,7 @@ class GroupBatch {
final String? id; final String? id;
final String? recipientGroupId; final String? recipientGroupId;
final String? userId; final String? userId;
final String? workspaceId;
final String? currency; final String? currency;
final String? description; final String? description;
final double? value; final double? value;
@@ -24,6 +25,7 @@ class GroupBatch {
this.id, this.id,
this.recipientGroupId, this.recipientGroupId,
this.userId, this.userId,
this.workspaceId,
this.currency, this.currency,
this.description, this.description,
this.value, this.value,
@@ -88,6 +90,7 @@ class GroupBatchItem {
class CreateBatchRequest { class CreateBatchRequest {
final String recipientGroupId; final String recipientGroupId;
final String userId; final String userId;
final String workspaceId;
final String currency; final String currency;
final String description; final String description;
final double amount; final double amount;
@@ -98,6 +101,7 @@ class CreateBatchRequest {
const CreateBatchRequest({ const CreateBatchRequest({
required this.recipientGroupId, required this.recipientGroupId,
required this.userId, required this.userId,
required this.workspaceId,
required this.currency, required this.currency,
required this.description, required this.description,
required this.amount, required this.amount,
@@ -114,12 +118,14 @@ class CreateBatchRequest {
@JsonSerializable() @JsonSerializable()
class BatchActionRequest { class BatchActionRequest {
final String userId; final String workspaceId;
final String idempotencyKey; final String idempotencyKey;
final String? authType;
const BatchActionRequest({ const BatchActionRequest({
required this.userId, required this.workspaceId,
required this.idempotencyKey, required this.idempotencyKey,
this.authType,
}); });
factory BatchActionRequest.fromJson(Map<String, dynamic> json) => factory BatchActionRequest.fromJson(Map<String, dynamic> json) =>
@@ -131,14 +137,14 @@ class BatchActionRequest {
@JsonSerializable(explicitToJson: true) @JsonSerializable(explicitToJson: true)
class GroupBatchUpdateRequest { class GroupBatchUpdateRequest {
String? id; String? id;
String? userId; String? workspaceId;
String? paymentProcessorLabel; String? paymentProcessorLabel;
String? paymentProcessorName; String? paymentProcessorName;
String? paymentProcessorImage; String? paymentProcessorImage;
GroupBatchUpdateRequest({ GroupBatchUpdateRequest({
this.id, this.id,
this.userId, this.workspaceId,
this.paymentProcessorLabel, this.paymentProcessorLabel,
this.paymentProcessorName, this.paymentProcessorName,
this.paymentProcessorImage, this.paymentProcessorImage,

View File

@@ -10,6 +10,7 @@ GroupBatch _$GroupBatchFromJson(Map<String, dynamic> json) => GroupBatch(
id: json['id'] as String?, id: json['id'] as String?,
recipientGroupId: json['recipientGroupId'] as String?, recipientGroupId: json['recipientGroupId'] as String?,
userId: json['userId'] as String?, userId: json['userId'] as String?,
workspaceId: json['workspaceId'] as String?,
currency: json['currency'] as String?, currency: json['currency'] as String?,
description: json['description'] as String?, description: json['description'] as String?,
value: (json['value'] as num?)?.toDouble(), value: (json['value'] as num?)?.toDouble(),
@@ -29,6 +30,7 @@ Map<String, dynamic> _$GroupBatchToJson(GroupBatch instance) =>
'id': instance.id, 'id': instance.id,
'recipientGroupId': instance.recipientGroupId, 'recipientGroupId': instance.recipientGroupId,
'userId': instance.userId, 'userId': instance.userId,
'workspaceId': instance.workspaceId,
'currency': instance.currency, 'currency': instance.currency,
'description': instance.description, 'description': instance.description,
'value': instance.value, 'value': instance.value,
@@ -83,6 +85,7 @@ CreateBatchRequest _$CreateBatchRequestFromJson(Map<String, dynamic> json) =>
CreateBatchRequest( CreateBatchRequest(
recipientGroupId: json['recipientGroupId'] as String, recipientGroupId: json['recipientGroupId'] as String,
userId: json['userId'] as String, userId: json['userId'] as String,
workspaceId: json['workspaceId'] as String,
currency: json['currency'] as String, currency: json['currency'] as String,
description: json['description'] as String, description: json['description'] as String,
amount: (json['amount'] as num).toDouble(), amount: (json['amount'] as num).toDouble(),
@@ -95,6 +98,7 @@ Map<String, dynamic> _$CreateBatchRequestToJson(CreateBatchRequest instance) =>
<String, dynamic>{ <String, dynamic>{
'recipientGroupId': instance.recipientGroupId, 'recipientGroupId': instance.recipientGroupId,
'userId': instance.userId, 'userId': instance.userId,
'workspaceId': instance.workspaceId,
'currency': instance.currency, 'currency': instance.currency,
'description': instance.description, 'description': instance.description,
'amount': instance.amount, 'amount': instance.amount,
@@ -105,21 +109,23 @@ Map<String, dynamic> _$CreateBatchRequestToJson(CreateBatchRequest instance) =>
BatchActionRequest _$BatchActionRequestFromJson(Map<String, dynamic> json) => BatchActionRequest _$BatchActionRequestFromJson(Map<String, dynamic> json) =>
BatchActionRequest( BatchActionRequest(
userId: json['userId'] as String, workspaceId: json['workspaceId'] as String,
idempotencyKey: json['idempotencyKey'] as String, idempotencyKey: json['idempotencyKey'] as String,
authType: json['authType'] as String?,
); );
Map<String, dynamic> _$BatchActionRequestToJson(BatchActionRequest instance) => Map<String, dynamic> _$BatchActionRequestToJson(BatchActionRequest instance) =>
<String, dynamic>{ <String, dynamic>{
'userId': instance.userId, 'workspaceId': instance.workspaceId,
'idempotencyKey': instance.idempotencyKey, 'idempotencyKey': instance.idempotencyKey,
'authType': instance.authType,
}; };
GroupBatchUpdateRequest _$GroupBatchUpdateRequestFromJson( GroupBatchUpdateRequest _$GroupBatchUpdateRequestFromJson(
Map<String, dynamic> json, Map<String, dynamic> json,
) => GroupBatchUpdateRequest( ) => GroupBatchUpdateRequest(
id: json['id'] as String?, id: json['id'] as String?,
userId: json['userId'] as String?, workspaceId: json['workspaceId'] as String?,
paymentProcessorLabel: json['paymentProcessorLabel'] as String?, paymentProcessorLabel: json['paymentProcessorLabel'] as String?,
paymentProcessorName: json['paymentProcessorName'] as String?, paymentProcessorName: json['paymentProcessorName'] as String?,
paymentProcessorImage: json['paymentProcessorImage'] as String?, paymentProcessorImage: json['paymentProcessorImage'] as String?,
@@ -129,7 +135,7 @@ Map<String, dynamic> _$GroupBatchUpdateRequestToJson(
GroupBatchUpdateRequest instance, GroupBatchUpdateRequest instance,
) => <String, dynamic>{ ) => <String, dynamic>{
'id': instance.id, 'id': instance.id,
'userId': instance.userId, 'workspaceId': instance.workspaceId,
'paymentProcessorLabel': instance.paymentProcessorLabel, 'paymentProcessorLabel': instance.paymentProcessorLabel,
'paymentProcessorName': instance.paymentProcessorName, 'paymentProcessorName': instance.paymentProcessorName,
'paymentProcessorImage': instance.paymentProcessorImage, 'paymentProcessorImage': instance.paymentProcessorImage,

View File

@@ -9,6 +9,7 @@ class RecipientGroup {
final String name; final String name;
final String? description; final String? description;
final String? userId; final String? userId;
final String? workspaceId;
final String? createdAt; final String? createdAt;
const RecipientGroup({ const RecipientGroup({
@@ -16,6 +17,7 @@ class RecipientGroup {
required this.name, required this.name,
this.description, this.description,
this.userId, this.userId,
this.workspaceId,
this.createdAt, this.createdAt,
}); });
@@ -56,12 +58,14 @@ class CreateGroupRequest {
final String name; final String name;
final String? description; final String? description;
final String userId; final String userId;
final String workspaceId;
final List<Recipient> recipients; final List<Recipient> recipients;
const CreateGroupRequest({ const CreateGroupRequest({
required this.name, required this.name,
this.description, this.description,
required this.userId, required this.userId,
required this.workspaceId,
required this.recipients, required this.recipients,
}); });

View File

@@ -12,6 +12,7 @@ RecipientGroup _$RecipientGroupFromJson(Map<String, dynamic> json) =>
name: json['name'] as String, name: json['name'] as String,
description: json['description'] as String?, description: json['description'] as String?,
userId: json['userId'] as String?, userId: json['userId'] as String?,
workspaceId: json['workspaceId'] as String?,
createdAt: json['createdAt'] as String?, createdAt: json['createdAt'] as String?,
); );
@@ -21,6 +22,7 @@ Map<String, dynamic> _$RecipientGroupToJson(RecipientGroup instance) =>
'name': instance.name, 'name': instance.name,
'description': instance.description, 'description': instance.description,
'userId': instance.userId, 'userId': instance.userId,
'workspaceId': instance.workspaceId,
'createdAt': instance.createdAt, 'createdAt': instance.createdAt,
}; };
@@ -53,6 +55,7 @@ CreateGroupRequest _$CreateGroupRequestFromJson(Map<String, dynamic> json) =>
name: json['name'] as String, name: json['name'] as String,
description: json['description'] as String?, description: json['description'] as String?,
userId: json['userId'] as String, userId: json['userId'] as String,
workspaceId: json['workspaceId'] as String,
recipients: (json['recipients'] as List<dynamic>) recipients: (json['recipients'] as List<dynamic>)
.map((e) => Recipient.fromJson(e as Map<String, dynamic>)) .map((e) => Recipient.fromJson(e as Map<String, dynamic>))
.toList(), .toList(),
@@ -63,5 +66,6 @@ Map<String, dynamic> _$CreateGroupRequestToJson(CreateGroupRequest instance) =>
'name': instance.name, 'name': instance.name,
'description': instance.description, 'description': instance.description,
'userId': instance.userId, 'userId': instance.userId,
'workspaceId': instance.workspaceId,
'recipients': instance.recipients.map((e) => e.toJson()).toList(), 'recipients': instance.recipients.map((e) => e.toJson()).toList(),
}; };

View File

@@ -90,10 +90,12 @@ class _BatchCreateScreenState extends State<BatchCreateScreen> {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final userId = prefs.getString('userId') ?? ''; final userId = prefs.getString('userId') ?? '';
final workspaceId = prefs.getString('workspaceId') ?? '';
final req = CreateBatchRequest( final req = CreateBatchRequest(
recipientGroupId: widget.group.id ?? '', recipientGroupId: widget.group.id ?? '',
userId: userId, userId: userId,
workspaceId: workspaceId,
currency: _currency, currency: _currency,
description: _descriptionController.text.trim(), description: _descriptionController.text.trim(),
amount: amount, amount: amount,
@@ -115,7 +117,7 @@ class _BatchCreateScreenState extends State<BatchCreateScreen> {
batch.id!, batch.id!,
selectedProvider, selectedProvider,
selectedProduct, selectedProduct,
userId, workspaceId,
); );
} }
@@ -130,7 +132,7 @@ class _BatchCreateScreenState extends State<BatchCreateScreen> {
String batchId, String batchId,
BillProvider provider, BillProvider provider,
BillProduct? product, BillProduct? product,
String userId, String workspaceId,
) async { ) async {
// Fetch the batch items so we can update them // Fetch the batch items so we can update them
final itemsResult = await batchController.listBatchItems(batchId); final itemsResult = await batchController.listBatchItems(batchId);
@@ -154,7 +156,7 @@ class _BatchCreateScreenState extends State<BatchCreateScreen> {
final updateList = GroupBatchItemUpdateList( final updateList = GroupBatchItemUpdateList(
items: updateItems, items: updateItems,
userId: userId, workspaceId: workspaceId,
); );
await batchController.updateBatchItems(batchId, updateList); await batchController.updateBatchItems(batchId, updateList);

View File

@@ -33,12 +33,13 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
ConfirmController? _confirmController; ConfirmController? _confirmController;
GroupBatchUpdateRequest? _cachedUpdateReq; GroupBatchUpdateRequest? _cachedUpdateReq;
bool _showPollButton = false; bool _showPollButton = false;
String _userId = ''; String _workspaceId = '';
bool _initialLoading = true; bool _initialLoading = true;
String? _loadError; String? _loadError;
PaymentProcessor? _selectedProcessor; PaymentProcessor? _selectedProcessor;
final TextEditingController _searchController = TextEditingController(); final TextEditingController _searchController = TextEditingController();
final TextEditingController _accountController = TextEditingController();
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
bool _editMode = false; bool _editMode = false;
@@ -85,9 +86,9 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
Future<void> _init() async { Future<void> _init() async {
final prefs = await SharedPreferences.getInstance(); 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 (!mounted) return;
if (result.isSuccess) { if (result.isSuccess) {
@@ -117,6 +118,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
@override @override
void dispose() { void dispose() {
_searchController.dispose(); _searchController.dispose();
_accountController.dispose();
_scrollController.dispose(); _scrollController.dispose();
_entryController.dispose(); _entryController.dispose();
_confirmController?.dispose(); _confirmController?.dispose();
@@ -136,7 +138,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
// Persist the selected payment processor on the batch before confirming // Persist the selected payment processor on the batch before confirming
final updateReq = GroupBatchUpdateRequest( final updateReq = GroupBatchUpdateRequest(
id: batch.id, id: batch.id,
userId: _userId, workspaceId: _workspaceId,
paymentProcessorLabel: processor.label, paymentProcessorLabel: processor.label,
paymentProcessorName: processor.name, paymentProcessorName: processor.name,
paymentProcessorImage: processor.image, paymentProcessorImage: processor.image,
@@ -152,7 +154,11 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
_cachedUpdateReq = updateReq; _cachedUpdateReq = updateReq;
// Step 2: Confirm the batch // 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 (!mounted) return;
if (!result.isSuccess) { if (!result.isSuccess) {
_showError(result.error ?? 'Confirm failed.'); _showError(result.error ?? 'Confirm failed.');
@@ -431,7 +437,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
final batch = _batch; final batch = _batch;
if (batch == null) return; if (batch == null) return;
final result = await controller.requestBatch(batch.id!, _userId); final result = await controller.requestBatch(batch.id!, _workspaceId);
if (!mounted) return; if (!mounted) return;
if (!result.isSuccess) { if (!result.isSuccess) {
@@ -484,7 +490,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
} }
} }
} else { } else {
final pollResult = await controller.pollBatch(batch.id!, _userId); final pollResult = await controller.pollBatch(batch.id!, _workspaceId);
if (!mounted) return; if (!mounted) return;
if (pollResult.isSuccess) { if (pollResult.isSuccess) {
setState(() => _showPollButton = false); setState(() => _showPollButton = false);
@@ -672,7 +678,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
Future<void> _onPoll() async { Future<void> _onPoll() async {
final batch = _batch; final batch = _batch;
if (batch == null) return; if (batch == null) return;
final result = await controller.pollBatch(batch.id!, _userId); final result = await controller.pollBatch(batch.id!, _workspaceId);
if (!mounted) return; if (!mounted) return;
if (result.isSuccess) { if (result.isSuccess) {
setState(() => _showPollButton = false); setState(() => _showPollButton = false);
@@ -685,7 +691,10 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
Future<void> _onDownloadReport() async { Future<void> _onDownloadReport() async {
final batch = _batch; final batch = _batch;
if (batch == null) return; if (batch == null) return;
final result = await controller.downloadBatchReport(batch.id!, _userId); final result = await controller.downloadBatchReport(
batch.id!,
_workspaceId,
);
if (!mounted) return; if (!mounted) return;
if (result.isSuccess) { if (result.isSuccess) {
final fileUrl = result.data?['fileUrl'] as String?; final fileUrl = result.data?['fileUrl'] as String?;
@@ -1435,7 +1444,10 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
if (items.isEmpty) return false; 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); final result = await controller.updateBatchItems(batch.id!, updateList);
if (!mounted) return false; if (!mounted) return false;
@@ -1492,6 +1504,8 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
onPressed: () { onPressed: () {
setSheetState(() { setSheetState(() {
controller.model.batchItemsStatusFilter = null; controller.model.batchItemsStatusFilter = null;
controller.model.batchItemsRecipientAccount = null;
_accountController.clear();
}); });
}, },
child: const Text('Clear All'), child: const Text('Clear All'),
@@ -1517,14 +1531,18 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
items: const [ items: const [
DropdownMenuItem(value: null, child: Text('All')), DropdownMenuItem(value: null, child: Text('All')),
DropdownMenuItem( DropdownMenuItem(
value: 'PENDING', value: 'CONFIRMED',
child: Text('Pending'), child: Text('CONFIRMED'),
), ),
DropdownMenuItem( DropdownMenuItem(
value: 'SUCCESS', value: 'INTEGRATED',
child: Text('Success'), child: Text('INTEGRATED'),
),
DropdownMenuItem(value: 'FAILED', child: Text('FAILED')),
DropdownMenuItem(
value: 'SKIPPED',
child: Text('SKIPPED'),
), ),
DropdownMenuItem(value: 'FAILED', child: Text('Failed')),
], ],
onChanged: (v) { onChanged: (v) {
setSheetState( setSheetState(
@@ -1532,6 +1550,24 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
); );
}, },
), ),
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), const SizedBox(height: 24),
SizedBox( SizedBox(
width: double.infinity, width: double.infinity,
@@ -1546,6 +1582,9 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
? _searchController.text ? _searchController.text
: null, : null,
status: controller.model.batchItemsStatusFilter, status: controller.model.batchItemsStatusFilter,
account: _accountController.text.isNotEmpty
? _accountController.text
: null,
); );
} }
}, },
@@ -2021,7 +2060,9 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
// Action Bar — search, filter, edit, refresh // Action Bar — search, filter, edit, refresh
// ────────────────────────────────────────────────────────────── // ──────────────────────────────────────────────────────────────
Widget _buildActionBar(ThemeData theme, bool isDark) { Widget _buildActionBar(ThemeData theme, bool isDark) {
final hasFilters = controller.model.batchItemsStatusFilter != null; final hasFilters =
controller.model.batchItemsStatusFilter != null ||
controller.model.batchItemsRecipientAccount != null;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -2033,7 +2074,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
child: TextFormField( child: TextFormField(
controller: _searchController, controller: _searchController,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'Search by name or phone', hintText: 'Search by recipient name',
prefixIcon: const Icon(Icons.search, size: 20), prefixIcon: const Icon(Icons.search, size: 20),
suffixIcon: _searchController.text.isNotEmpty suffixIcon: _searchController.text.isNotEmpty
? IconButton( ? IconButton(
@@ -2046,6 +2087,9 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
batchId, batchId,
status: status:
controller.model.batchItemsStatusFilter, controller.model.batchItemsStatusFilter,
account: controller
.model
.batchItemsRecipientAccount,
); );
} }
}, },
@@ -2075,6 +2119,7 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
batchId, batchId,
search: value.isNotEmpty ? value : null, search: value.isNotEmpty ? value : null,
status: controller.model.batchItemsStatusFilter, status: controller.model.batchItemsStatusFilter,
account: controller.model.batchItemsRecipientAccount,
); );
} }
}, },
@@ -2147,6 +2192,26 @@ class _BatchDetailScreenState extends State<BatchDetailScreen>
search: _searchController.text.isNotEmpty search: _searchController.text.isNotEmpty
? _searchController.text ? _searchController.text
: null, : 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,
); );
} }
}, },

View File

@@ -65,10 +65,12 @@ class _CreateGroupFromFileScreenState extends State<CreateGroupFromFileScreen> {
} }
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final workspaceId = prefs.getString('workspaceId') ?? '';
final userId = prefs.getString('userId') ?? ''; final userId = prefs.getString('userId') ?? '';
final result = await controller.createGroupFromFile( final result = await controller.createGroupFromFile(
groupName: _groupNameController.text.trim(), groupName: _groupNameController.text.trim(),
workspaceId: workspaceId,
userId: userId, userId: userId,
description: _descriptionController.text.trim().isEmpty description: _descriptionController.text.trim().isEmpty
? null ? null

View File

@@ -82,6 +82,7 @@ class _GroupCreateScreenState extends State<GroupCreateScreen> {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
final userId = prefs.getString('userId') ?? ''; final userId = prefs.getString('userId') ?? '';
final workspaceId = prefs.getString('workspaceId') ?? '';
final req = CreateGroupRequest( final req = CreateGroupRequest(
name: _nameController.text.trim(), name: _nameController.text.trim(),
@@ -89,6 +90,7 @@ class _GroupCreateScreenState extends State<GroupCreateScreen> {
? null ? null
: _descriptionController.text.trim(), : _descriptionController.text.trim(),
userId: userId, userId: userId,
workspaceId: workspaceId,
recipients: recipients, recipients: recipients,
); );

View File

@@ -552,7 +552,7 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
await groupController.removeMember( await groupController.removeMember(
groupId: widget.group.id ?? '', groupId: widget.group.id ?? '',
recipientId: member.id ?? '', recipientId: member.id ?? '',
userId: widget.group.userId ?? '', workspaceId: widget.group.workspaceId ?? '',
); );
} }
} }
@@ -675,7 +675,7 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
} }
void _onDeleteSelected() async { void _onDeleteSelected() async {
final userId = widget.group.userId ?? ''; final workspaceId = widget.group.workspaceId ?? '';
final count = batchController.model.selectedBatchIds.length; final count = batchController.model.selectedBatchIds.length;
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
@@ -700,7 +700,7 @@ class _GroupDetailScreenState extends State<GroupDetailScreen>
if (confirmed == true) { if (confirmed == true) {
await batchController.deleteSelectedBatches( await batchController.deleteSelectedBatches(
groupId: widget.group.id ?? '', groupId: widget.group.id ?? '',
userId: userId, workspaceId: workspaceId,
); );
} }
} }

View File

@@ -30,26 +30,28 @@ class _GroupsScreenState extends State<GroupsScreen> {
void _onScroll() { void _onScroll() {
if (_scrollController.position.pixels >= if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) { _scrollController.position.maxScrollExtent - 200) {
controller.loadNextPage(userId: prefs.getString('userId') ?? ''); controller.loadNextPage(
workspaceId: prefs.getString('workspaceId') ?? '',
);
} }
} }
Future<void> _loadData() async { Future<void> _loadData() async {
prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
final userId = prefs.getString('userId') ?? ''; final workspaceId = prefs.getString('workspaceId') ?? '';
await controller.listGroups(userId: userId); await controller.listGroups(workspaceId: workspaceId);
} }
void _onSearchChanged(String value) { void _onSearchChanged(String value) {
final userId = prefs.getString('userId') ?? ''; final workspaceId = prefs.getString('workspaceId') ?? '';
controller.searchGroups( controller.searchGroups(
userId: userId, workspaceId: workspaceId,
name: value.isNotEmpty ? value : null, name: value.isNotEmpty ? value : null,
); );
} }
void _onDeleteSelected() async { void _onDeleteSelected() async {
final userId = prefs.getString('userId') ?? ''; final workspaceId = prefs.getString('workspaceId') ?? '';
final count = controller.model.selectedGroupIds.length; final count = controller.model.selectedGroupIds.length;
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
@@ -72,7 +74,7 @@ class _GroupsScreenState extends State<GroupsScreen> {
); );
if (confirmed == true) { if (confirmed == true) {
await controller.deleteSelectedGroups(userId: userId); await controller.deleteSelectedGroups(workspaceId: workspaceId);
} }
} }

View File

@@ -66,7 +66,7 @@ class HistoryController extends ChangeNotifier {
} }
_safeNotify(); _safeNotify();
params['partyType'] = 'INDIVIDUAL'; params['partyType'] = 'INDIVIDUAL,GROUP';
try { try {
Map<String, dynamic> response = await http.get( Map<String, dynamic> response = await http.get(
@@ -107,7 +107,7 @@ class HistoryController extends ChangeNotifier {
} }
Future<void> searchTransactions( Future<void> searchTransactions(
String userId, { String workspaceId, {
String? search, String? search,
String? status, String? status,
String? type, String? type,
@@ -116,7 +116,7 @@ class HistoryController extends ChangeNotifier {
model.statusFilter = status; model.statusFilter = status;
model.typeFilter = type; model.typeFilter = type;
final params = <String, String>{ final params = <String, String>{
'userId': userId, 'workspaceId': workspaceId,
'page': '0', 'page': '0',
'size': '20', 'size': '20',
'sort': 'createdAt,desc', 'sort': 'createdAt,desc',
@@ -138,12 +138,12 @@ class HistoryController extends ChangeNotifier {
await getTransactions(params); await getTransactions(params);
} }
Future<void> loadNextPage(String userId) async { Future<void> loadNextPage(String workspaceId) async {
if (model.isLoadingMore || model.currentPage + 1 >= model.totalPages) { if (model.isLoadingMore || model.currentPage + 1 >= model.totalPages) {
return; return;
} }
final params = <String, String>{ final params = <String, String>{
'userId': userId, 'workspaceId': workspaceId,
'page': (model.currentPage + 1).toString(), 'page': (model.currentPage + 1).toString(),
'size': '20', 'size': '20',
'sort': 'createdAt,desc', 'sort': 'createdAt,desc',

View File

@@ -64,16 +64,16 @@ class _HistoryScreenState extends State<HistoryScreen>
Future<void> setupData() async { Future<void> setupData() async {
prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
final userId = prefs.getString("userId") ?? ''; final workspaceId = prefs.getString("workspaceId") ?? '';
await controller.searchTransactions(userId); await controller.searchTransactions(workspaceId);
} }
void _onScroll() { void _onScroll() {
if (_scrollController.position.pixels >= if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) { _scrollController.position.maxScrollExtent - 200) {
final userId = prefs.getString("userId") ?? ''; final workspaceId = prefs.getString("workspaceId") ?? '';
if (userId.isNotEmpty) { if (workspaceId.isNotEmpty) {
controller.loadNextPage(userId); controller.loadNextPage(workspaceId);
} }
} }
} }
@@ -227,9 +227,10 @@ class _HistoryScreenState extends State<HistoryScreen>
child: ElevatedButton( child: ElevatedButton(
onPressed: () { onPressed: () {
Navigator.of(ctx).pop(); Navigator.of(ctx).pop();
final userId = prefs.getString("userId") ?? ''; final workspaceId =
prefs.getString("workspaceId") ?? '';
controller.searchTransactions( controller.searchTransactions(
userId, workspaceId,
search: _searchController.text.isNotEmpty search: _searchController.text.isNotEmpty
? _searchController.text ? _searchController.text
: null, : null,
@@ -327,9 +328,10 @@ class _HistoryScreenState extends State<HistoryScreen>
icon: const Icon(Icons.clear), icon: const Icon(Icons.clear),
onPressed: () { onPressed: () {
_searchController.clear(); _searchController.clear();
final userId = prefs.getString("userId") ?? ''; final workspaceId =
prefs.getString("workspaceId") ?? '';
controller.searchTransactions( controller.searchTransactions(
userId, workspaceId,
status: controller.model.statusFilter, status: controller.model.statusFilter,
type: controller.model.typeFilter, type: controller.model.typeFilter,
); );
@@ -351,9 +353,9 @@ class _HistoryScreenState extends State<HistoryScreen>
), ),
textInputAction: TextInputAction.search, textInputAction: TextInputAction.search,
onFieldSubmitted: (value) { onFieldSubmitted: (value) {
final userId = prefs.getString("userId") ?? ''; final workspaceId = prefs.getString("workspaceId") ?? '';
controller.searchTransactions( controller.searchTransactions(
userId, workspaceId,
search: value.isNotEmpty ? value : null, search: value.isNotEmpty ? value : null,
status: controller.model.statusFilter, status: controller.model.statusFilter,
type: controller.model.typeFilter, type: controller.model.typeFilter,
@@ -496,9 +498,9 @@ class _HistoryScreenState extends State<HistoryScreen>
'Status: ${controller.model.statusFilter}', 'Status: ${controller.model.statusFilter}',
() { () {
controller.model.statusFilter = null; controller.model.statusFilter = null;
final userId = prefs.getString("userId") ?? ''; final workspaceId = prefs.getString("workspaceId") ?? '';
controller.searchTransactions( controller.searchTransactions(
userId, workspaceId,
search: _searchController.text.isNotEmpty search: _searchController.text.isNotEmpty
? _searchController.text ? _searchController.text
: null, : null,
@@ -509,9 +511,9 @@ class _HistoryScreenState extends State<HistoryScreen>
if (controller.model.typeFilter != null) if (controller.model.typeFilter != null)
_activeFilterChip('Type: ${controller.model.typeFilter}', () { _activeFilterChip('Type: ${controller.model.typeFilter}', () {
controller.model.typeFilter = null; controller.model.typeFilter = null;
final userId = prefs.getString("userId") ?? ''; final workspaceId = prefs.getString("workspaceId") ?? '';
controller.searchTransactions( controller.searchTransactions(
userId, workspaceId,
search: _searchController.text.isNotEmpty search: _searchController.text.isNotEmpty
? _searchController.text ? _searchController.text
: null, : null,

View File

@@ -148,14 +148,14 @@ class HomeController extends ChangeNotifier {
} }
Future<ApiResponse<List<Map<String, dynamic>>>> getTransactions( Future<ApiResponse<List<Map<String, dynamic>>>> getTransactions(
String userId, String workspaceId,
) async { ) async {
model.isLoading = true; model.isLoading = true;
if (!_disposed) notifyListeners(); if (!_disposed) notifyListeners();
try { try {
Map<String, dynamic> response = await http.get( Map<String, dynamic> 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); PageableModel pageableModel = PageableModel.fromJson(response);

View File

@@ -127,11 +127,11 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
}); });
} }
if (prefs.getString("userId") == null) { if (prefs.getString("workspaceId") == null) {
prefs.setString("userId", Uuid().v4()); prefs.setString("workspaceId", Uuid().v4());
} }
await homeController.getTransactions(prefs.getString("userId")!); await homeController.getTransactions(prefs.getString("workspaceId")!);
} }
void _onCurrencySelected(String currency) { void _onCurrencySelected(String currency) {
@@ -155,8 +155,8 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
await homeController.getProviders(_selectedCurrency); await homeController.getProviders(_selectedCurrency);
prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
if (prefs.getString("userId") != null) { if (prefs.getString("workspaceId") != null) {
await homeController.getTransactions(prefs.getString("userId")!); await homeController.getTransactions(prefs.getString("workspaceId")!);
} }
} }
@@ -165,11 +165,11 @@ class _HomeScreenState extends State<HomeScreen> with TickerProviderStateMixin {
if (location.startsWith('/home')) { if (location.startsWith('/home')) {
prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
if (prefs.getString("userId") == null) { if (prefs.getString("workspaceId") == null) {
prefs.setString("userId", Uuid().v4()); prefs.setString("workspaceId", Uuid().v4());
} }
await homeController.getTransactions(prefs.getString("userId")!); await homeController.getTransactions(prefs.getString("workspaceId")!);
} }
} }

View File

@@ -104,7 +104,7 @@ class _LandingScreenState extends State<LandingScreen> {
Center( Center(
child: TextButton( child: TextButton(
onPressed: () { onPressed: () {
context.go('/'); context.go('/workspace');
}, },
child: Text( child: Text(
'Continue as guest?', 'Continue as guest?',

View File

@@ -78,7 +78,7 @@ class _LoginScreenState extends State<LoginScreen> {
if (!mounted) return; if (!mounted) return;
if (response.isSuccess) { if (response.isSuccess) {
context.go('/'); context.go('/workspace');
} }
} }

View File

@@ -99,6 +99,7 @@ class PayController extends ChangeNotifier {
paymentProcessorImage: model.selectedPaymentProcessor?.image ?? '', paymentProcessorImage: model.selectedPaymentProcessor?.image ?? '',
providerImage: model.selectedProvider?.image ?? '', providerImage: model.selectedProvider?.image ?? '',
providerLabel: model.selectedProvider?.label ?? '', providerLabel: model.selectedProvider?.label ?? '',
workspaceId: prefs.getString("workspaceId") ?? '',
userId: prefs.getString("userId") ?? '', userId: prefs.getString("userId") ?? '',
creditName: transactionController.model.formData.creditName, creditName: transactionController.model.formData.creditName,
creditEmail: transactionController.model.formData.creditEmail, creditEmail: transactionController.model.formData.creditEmail,

View File

@@ -13,7 +13,7 @@ part of 'pay_controller.dart';
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
/// @nodoc /// @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; 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 /// Create a copy of PaymentProcessor
@@ -25,6 +25,12 @@ $PaymentProcessorCopyWith<PaymentProcessor> get copyWith => _$PaymentProcessorCo
/// Serializes this PaymentProcessor to a JSON map. /// Serializes this PaymentProcessor to a JSON map.
Map<String, dynamic> toJson(); Map<String, dynamic> 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 @override
bool operator ==(Object other) { 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); int get hashCode => Object.hash(runtimeType,name,description,image,accountFieldName,accountFieldLabel,label,authType);
@override @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)'; 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 /// @nodoc
@JsonSerializable() @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}); 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<String, dynamic> json) => _$PaymentProcessorFromJson(json); factory _PaymentProcessor.fromJson(Map<String, dynamic> json) => _$PaymentProcessorFromJson(json);
@@ -236,6 +242,12 @@ _$PaymentProcessorCopyWith<_PaymentProcessor> get copyWith => __$PaymentProcesso
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return _$PaymentProcessorToJson(this, ); 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 @override
bool operator ==(Object other) { 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); int get hashCode => Object.hash(runtimeType,name,description,image,accountFieldName,accountFieldLabel,label,authType);
@override @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)'; return 'PaymentProcessor(name: $name, description: $description, image: $image, accountFieldName: $accountFieldName, accountFieldLabel: $accountFieldLabel, label: $label, authType: $authType)';
} }
@@ -294,7 +306,7 @@ as String,
/// @nodoc /// @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; String get uid; String get name; String get description; String get displayName; double get defaultAmount; bool get requiresAmount;
/// Create a copy of BillProduct /// Create a copy of BillProduct
@@ -306,6 +318,12 @@ $BillProductCopyWith<BillProduct> get copyWith => _$BillProductCopyWithImpl<Bill
/// Serializes this BillProduct to a JSON map. /// Serializes this BillProduct to a JSON map.
Map<String, dynamic> toJson(); Map<String, dynamic> 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 @override
bool operator ==(Object other) { bool operator ==(Object other) {
@@ -317,7 +335,7 @@ bool operator ==(Object other) {
int get hashCode => Object.hash(runtimeType,uid,name,description,displayName,defaultAmount,requiresAmount); int get hashCode => Object.hash(runtimeType,uid,name,description,displayName,defaultAmount,requiresAmount);
@override @override
String toString() { String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'BillProduct(uid: $uid, name: $name, description: $description, displayName: $displayName, defaultAmount: $defaultAmount, requiresAmount: $requiresAmount)'; 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 /// @nodoc
@JsonSerializable() @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}); const _BillProduct({required this.uid, required this.name, required this.description, required this.displayName, required this.defaultAmount, required this.requiresAmount});
factory _BillProduct.fromJson(Map<String, dynamic> json) => _$BillProductFromJson(json); factory _BillProduct.fromJson(Map<String, dynamic> json) => _$BillProductFromJson(json);
@@ -515,6 +533,12 @@ _$BillProductCopyWith<_BillProduct> get copyWith => __$BillProductCopyWithImpl<_
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return _$BillProductToJson(this, ); 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 @override
bool operator ==(Object other) { bool operator ==(Object other) {
@@ -526,7 +550,7 @@ bool operator ==(Object other) {
int get hashCode => Object.hash(runtimeType,uid,name,description,displayName,defaultAmount,requiresAmount); int get hashCode => Object.hash(runtimeType,uid,name,description,displayName,defaultAmount,requiresAmount);
@override @override
String toString() { String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'BillProduct(uid: $uid, name: $name, description: $description, displayName: $displayName, defaultAmount: $defaultAmount, requiresAmount: $requiresAmount)'; return 'BillProduct(uid: $uid, name: $name, description: $description, displayName: $displayName, defaultAmount: $defaultAmount, requiresAmount: $requiresAmount)';
} }

View File

@@ -125,8 +125,12 @@ class ReceiptController extends ChangeNotifier {
Future<ApiResponse<TransactionModel>> getReceiptData(String id) async { Future<ApiResponse<TransactionModel>> getReceiptData(String id) async {
setLoading(true); setLoading(true);
final prefs = await SharedPreferences.getInstance();
try { try {
Map<String, dynamic> response = await http.get('/public/transaction/$id'); Map<String, dynamic> response = await http.get(
'/public/transaction/$id?workspaceId=${prefs.getString("workspaceId")}',
);
final transaction = TransactionModel.fromJson(response); final transaction = TransactionModel.fromJson(response);
model.transaction = transaction; model.transaction = transaction;
setLoading(false); setLoading(false);

View File

@@ -1,3 +1,4 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:qpay/http/http.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:freezed_annotation/freezed_annotation.dart';
import 'package:qpay/screens/transaction_controller.dart'; import 'package:qpay/screens/transaction_controller.dart';
import '../../models/pageable_model.dart';
part 'recipients_controller.freezed.dart'; part 'recipients_controller.freezed.dart';
part 'recipients_controller.g.dart'; part 'recipients_controller.g.dart';
@@ -56,18 +59,25 @@ class RecipientsController extends ChangeNotifier {
notifyListeners(); notifyListeners();
try { try {
List<dynamic> response = await http.get( final response = await http.get(
'/public/recipients/search?${buildQueryParameters(params ?? {})}', '/public/recipients?${buildQueryParameters(params ?? {})}',
); );
final page = PageableModel.fromJson(response as Map<String, dynamic>);
model.recipients.clear(); 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; model.isLoading = false;
notifyListeners(); notifyListeners();
return ApiResponse.success(List<Recipient>.from(model.recipients)); return ApiResponse.success(model.recipients);
} catch (e) { } catch (e, s) {
if (kDebugMode) {
logger.e(e);
logger.e(s);
}
model.errorMessage = e.toString(); model.errorMessage = e.toString();
model.recipients = []; model.recipients = [];
logger.e(e);
model.isLoading = false; model.isLoading = false;
notifyListeners(); notifyListeners();
return ApiResponse.failure( return ApiResponse.failure(

View File

@@ -13,7 +13,7 @@ part of 'recipients_controller.dart';
T _$identity<T>(T value) => value; T _$identity<T>(T value) => value;
/// @nodoc /// @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; @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 /// Create a copy of Recipient
@@ -25,6 +25,12 @@ $RecipientCopyWith<Recipient> get copyWith => _$RecipientCopyWithImpl<Recipient>
/// Serializes this Recipient to a JSON map. /// Serializes this Recipient to a JSON map.
Map<String, dynamic> toJson(); Map<String, dynamic> 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 @override
bool operator ==(Object other) { 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); int get hashCode => Object.hash(runtimeType,uid,name,email,phoneNumber,address,account,initials,latestProviderLabel);
@override @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)'; 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 /// @nodoc
@JsonSerializable() @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}); 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<String, dynamic> json) => _$RecipientFromJson(json); factory _Recipient.fromJson(Map<String, dynamic> json) => _$RecipientFromJson(json);
@@ -238,6 +244,12 @@ _$RecipientCopyWith<_Recipient> get copyWith => __$RecipientCopyWithImpl<_Recipi
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
return _$RecipientToJson(this, ); 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 @override
bool operator ==(Object other) { 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); int get hashCode => Object.hash(runtimeType,uid,name,email,phoneNumber,address,account,initials,latestProviderLabel);
@override @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)'; return 'Recipient(uid: $uid, name: $name, email: $email, phoneNumber: $phoneNumber, address: $address, account: $account, initials: $initials, latestProviderLabel: $latestProviderLabel)';
} }

View File

@@ -66,7 +66,7 @@ class _RecipientsScreenState extends State<RecipientsScreen>
Future<void> setupData() async { Future<void> setupData() async {
prefs = await SharedPreferences.getInstance(); prefs = await SharedPreferences.getInstance();
_queryParams['userId'] = prefs.getString("userId")!; _queryParams['workspaceId'] = prefs.getString("workspaceId")!;
controller.getRecipients(_queryParams); controller.getRecipients(_queryParams);
} }
@@ -268,7 +268,7 @@ class _RecipientsScreenState extends State<RecipientsScreen>
_queryParams['email'] = _accountController.text; _queryParams['email'] = _accountController.text;
_queryParams['phoneNumber'] = _accountController.text; _queryParams['phoneNumber'] = _accountController.text;
_queryParams['name'] = _accountController.text; _queryParams['name'] = _accountController.text;
_queryParams['userId'] = prefs.getString("userId")!; _queryParams['workspaceId'] = prefs.getString("workspaceId")!;
controller.getRecipients(_queryParams); controller.getRecipients(_queryParams);
}); });

View File

@@ -39,6 +39,7 @@ class TransactionModel {
paymentProcessorImage: '', paymentProcessorImage: '',
providerImage: '', providerImage: '',
userId: '', userId: '',
workspaceId: '',
providerLabel: '', providerLabel: '',
region: '', region: '',
); );
@@ -69,6 +70,7 @@ abstract class FormData with _$FormData {
required String providerImage, required String providerImage,
required String providerLabel, required String providerLabel,
required String userId, required String userId,
required String workspaceId,
String? debitPhone, String? debitPhone,
String? debitAccount, String? debitAccount,
String? productUid, String? productUid,
@@ -150,6 +152,7 @@ class TransactionController extends ChangeNotifier {
paymentProcessorImage: '', paymentProcessorImage: '',
providerImage: '', providerImage: '',
userId: '', userId: '',
workspaceId: '',
providerLabel: '', providerLabel: '',
region: '', region: '',
); );

View File

@@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
mixin _$FormData { mixin _$FormData {
String? get id; String get type;// CONFIRM, REQUEST, REVERSE 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 /// Create a copy of FormData
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -29,16 +29,16 @@ $FormDataCopyWith<FormData> get copyWith => _$FormDataCopyWithImpl<FormData>(thi
@override @override
bool operator ==(Object other) { 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) @JsonKey(includeFromJson: false, includeToJson: false)
@override @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 @override
String toString() { 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; factory $FormDataCopyWith(FormData value, $Res Function(FormData) _then) = _$FormDataCopyWithImpl;
@useResult @useResult
$Res call({ $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 /// Create a copy of FormData
/// with the given fields replaced by the non-null parameter values. /// 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( return _then(_self.copyWith(
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable 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 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,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,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,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,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?,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 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 extends Object?>(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 extends Object?>(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) { switch (_that) {
case _FormData() when $default != null: 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(); return orElse();
} }
@@ -204,10 +205,10 @@ return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debi
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>(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 extends Object?>(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) { switch (_that) {
case _FormData(): 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'); throw StateError('Unexpected subclass');
} }
@@ -224,10 +225,10 @@ return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debi
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(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 extends Object?>(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) { switch (_that) {
case _FormData() when $default != null: 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; return null;
} }
@@ -239,7 +240,7 @@ return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debi
@JsonSerializable() @JsonSerializable()
class _FormData implements FormData { 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<String, dynamic> json) => _$FormDataFromJson(json); factory _FormData.fromJson(Map<String, dynamic> json) => _$FormDataFromJson(json);
@override final String? id; @override final String? id;
@@ -262,6 +263,7 @@ class _FormData implements FormData {
@override final String providerImage; @override final String providerImage;
@override final String providerLabel; @override final String providerLabel;
@override final String userId; @override final String userId;
@override final String workspaceId;
@override final String? debitPhone; @override final String? debitPhone;
@override final String? debitAccount; @override final String? debitAccount;
@override final String? productUid; @override final String? productUid;
@@ -287,16 +289,16 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { 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) @JsonKey(includeFromJson: false, includeToJson: false)
@override @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 @override
String toString() { 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; factory _$FormDataCopyWith(_FormData value, $Res Function(_FormData) _then) = __$FormDataCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $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 /// Create a copy of FormData
/// with the given fields replaced by the non-null parameter values. /// 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( return _then(_FormData(
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable 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 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,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,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,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,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?,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 as String?,productUid: freezed == productUid ? _self.productUid : productUid // ignore: cast_nullable_to_non_nullable

View File

@@ -26,6 +26,7 @@ _FormData _$FormDataFromJson(Map<String, dynamic> json) => _FormData(
providerImage: json['providerImage'] as String, providerImage: json['providerImage'] as String,
providerLabel: json['providerLabel'] as String, providerLabel: json['providerLabel'] as String,
userId: json['userId'] as String, userId: json['userId'] as String,
workspaceId: json['workspaceId'] as String,
debitPhone: json['debitPhone'] as String?, debitPhone: json['debitPhone'] as String?,
debitAccount: json['debitAccount'] as String?, debitAccount: json['debitAccount'] as String?,
productUid: json['productUid'] as String?, productUid: json['productUid'] as String?,
@@ -59,6 +60,7 @@ Map<String, dynamic> _$FormDataToJson(_FormData instance) => <String, dynamic>{
'providerImage': instance.providerImage, 'providerImage': instance.providerImage,
'providerLabel': instance.providerLabel, 'providerLabel': instance.providerLabel,
'userId': instance.userId, 'userId': instance.userId,
'workspaceId': instance.workspaceId,
'debitPhone': instance.debitPhone, 'debitPhone': instance.debitPhone,
'debitAccount': instance.debitAccount, 'debitAccount': instance.debitAccount,
'productUid': instance.productUid, 'productUid': instance.productUid,

View File

@@ -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<String, dynamic> 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<String, dynamic> toJson() {
return {
'id': id,
'createdAt': createdAt,
'updatedAt': updatedAt,
'deleted': deleted,
'name': name,
'description': description,
'phone': phone,
'email': email,
};
}
}

View File

@@ -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<WorkspaceProvider>();
/// await provider.fetchWorkspaces(userId: '...');
/// ```
class WorkspaceProvider extends ChangeNotifier {
final Http _http = Http();
WorkspaceStatus _status = WorkspaceStatus.idle;
List<WorkspaceModel> _workspaces = [];
String? _error;
WorkspaceStatus get status => _status;
List<WorkspaceModel> 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<void> 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<String, dynamic>;
final content = body['content'] as List<dynamic>;
_workspaces = content
.map((e) => WorkspaceModel.fromJson(e as Map<String, dynamic>))
.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();
}
}

View File

@@ -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<WorkspaceScreen> createState() => _WorkspaceScreenState();
}
class _WorkspaceScreenState extends State<WorkspaceScreen>
with TickerProviderStateMixin {
late AnimationController _fadeController;
late Animation<double> _fadeAnimation;
late Animation<Offset> _slideAnimation;
AuthState authState = AuthState();
bool _navigated = false;
@override
void initState() {
super.initState();
_fadeController = AnimationController(
duration: const Duration(milliseconds: 400),
vsync: this,
);
_fadeAnimation = Tween<double>(
begin: 0.0,
end: 1.0,
).animate(CurvedAnimation(parent: _fadeController, curve: Curves.easeOut));
_slideAnimation =
Tween<Offset>(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<void> _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<void> _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<WorkspaceProvider>();
// 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<WorkspaceProvider>(
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),
],
),
),
),
);
}
}