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

View File

@@ -7,6 +7,7 @@ import 'package:qpay/url_strategy.dart';
import 'package:qpay/screens/transaction_controller.dart';
import 'package:qpay/screens/onboarding/onboarding_controller.dart';
import 'package:qpay/providers/splash_provider.dart';
import 'package:qpay/screens/workspaces/workspace_provider.dart';
import 'package:qpay/theme/app_theme.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:firebase_core/firebase_core.dart';
@@ -53,6 +54,9 @@ void main() async {
ChangeNotifierProvider<SplashProvider>(
create: (_) => SplashProvider(),
),
ChangeNotifierProvider<WorkspaceProvider>(
create: (_) => WorkspaceProvider(),
),
],
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/recipient/recipients_screen.dart';
import 'package:qpay/screens/splash_screen.dart';
import 'package:qpay/screens/workspaces/workspace_screen.dart';
/// Tracks whether the splash animation is complete so that
/// GoRouter's redirect can show the splash first, then release
@@ -135,9 +136,10 @@ GoRouter buildRouter() {
return authState.requireAuth(path);
}
// Splash → original route or home.
// Splash → workspace selector (which may auto-redirect to home
// if only one workspace exists).
if (path == '/splash') {
return splashState.intendedRoute ?? '/';
return '/workspace';
}
}
@@ -153,12 +155,13 @@ GoRouter buildRouter() {
ShellRoute(
builder: (context, state, child) {
final location = GoRouterState.of(context).uri.toString();
if (location.startsWith('/onboarding/')) {
if (location.startsWith('/onboarding/') || location == '/workspace') {
return child;
}
return ScaffoldWithNavBar(child: child);
},
routes: [
GoRoute(path: '/workspace', builder: (context, state) => const WorkspaceScreen()),
GoRoute(path: '/', builder: (context, state) => const HomeScreen()),
GoRoute(
path: '/home',
@@ -229,8 +232,7 @@ GoRouter buildRouter() {
),
GoRoute(
path: '/groups/create-from-file',
builder: (context, state) =>
const CreateGroupFromFileScreen(),
builder: (context, state) => const CreateGroupFromFileScreen(),
),
GoRoute(
path: '/groups/:groupId',

View File

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

View File

@@ -23,7 +23,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
late AccountProvider _accountProvider;
bool _isLoggedIn = false;
String? _phoneNumber;
String? _workspaceId;
AccountModel? _usdAccount;
AccountModel? _zwgAccount;
@@ -31,6 +31,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
bool _isLoadingZwG = false;
String? _usdError;
String? _zwgError;
bool _showAccountError = false;
@override
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;
}
@@ -67,9 +68,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
});
// Fetch USD balance
final usdResult = await _accountProvider.fetchAccount(
'USD$_phoneNumber',
);
final usdResult = await _accountProvider.fetchAccount(_workspaceId, 'USD');
if (mounted) {
setState(() {
_isLoadingUsd = false;
@@ -83,9 +82,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
}
// Fetch ZWG balance
final zwgResult = await _accountProvider.fetchAccount(
'ZWG$_phoneNumber',
);
final zwgResult = await _accountProvider.fetchAccount(_workspaceId, 'ZWG');
if (mounted) {
setState(() {
_isLoadingZwG = false;
@@ -104,25 +101,23 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
!zwgResult.isSuccess &&
_isLoggedIn) {
setState(() {
_isLoggedIn = false;
_showAccountError = true;
});
}
}
Future<void> _refreshBalances() async {
final prefs = await SharedPreferences.getInstance();
_phoneNumber = prefs.getString('phone');
_workspaceId = prefs.getString('workspaceId');
if (_phoneNumber == null || _phoneNumber!.isEmpty) return;
if (_workspaceId == null || _workspaceId!.isEmpty) return;
setState(() {
_isLoadingUsd = true;
_isLoadingZwG = true;
});
final usdResult = await _accountProvider.fetchAccount(
'USD$_phoneNumber',
);
final usdResult = await _accountProvider.fetchAccount(_workspaceId, 'USD');
if (mounted) {
setState(() {
_isLoadingUsd = false;
@@ -135,9 +130,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
});
}
final zwgResult = await _accountProvider.fetchAccount(
'ZWG$_phoneNumber',
);
final zwgResult = await _accountProvider.fetchAccount(_workspaceId, 'ZWG');
if (mounted) {
setState(() {
_isLoadingZwG = false;
@@ -155,7 +148,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
!zwgResult.isSuccess &&
_isLoggedIn) {
setState(() {
_isLoggedIn = false;
_showAccountError = true;
});
}
}
@@ -169,10 +162,47 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
return _buildLoginPrompt(theme, isDark);
}
if (_phoneNumber == null || _phoneNumber!.isEmpty) {
if (_workspaceId == null || _workspaceId!.isEmpty) {
return const SizedBox.shrink();
}
if (_showAccountError) {
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(vertical: 14, horizontal: 18),
decoration: BoxDecoration(
color: theme.colorScheme.error.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: theme.colorScheme.error.withValues(alpha: 0.3),
width: 1,
),
),
child: Row(
children: [
Icon(
Icons.error_outline_rounded,
color: theme.colorScheme.error.withValues(alpha: 0.7),
size: 16,
),
const SizedBox(width: 8),
Expanded(
child: Text(
'Unable to load account balances. Please try refreshing.',
style: TextStyle(
fontSize: 12,
color: theme.colorScheme.error.withValues(alpha: 0.7),
),
),
),
],
),
),
);
}
return Padding(
padding: const EdgeInsets.only(bottom: 12),
child: Column(
@@ -231,9 +261,7 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
: theme.colorScheme.primary,
),
label: Text(
_isLoadingUsd || _isLoadingZwG
? 'Refreshing...'
: 'Refresh',
_isLoadingUsd || _isLoadingZwG ? 'Refreshing...' : 'Refresh',
style: TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
@@ -301,7 +329,10 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
style: ElevatedButton.styleFrom(
backgroundColor: theme.colorScheme.primary,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 8,
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
@@ -343,18 +374,17 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
boxShadow: [
BoxShadow(
color: gradientColors[0].withValues(alpha: isSelected ? 0.5 : 0.25),
blurRadius: isSelected ? 12 : 6,
offset: Offset(0, isSelected ? 4 : 2),
),
],
boxShadow: [
BoxShadow(
color: gradientColors[0].withValues(
alpha: isSelected ? 0.5 : 0.25,
),
blurRadius: isSelected ? 12 : 6,
offset: Offset(0, isSelected ? 4 : 2),
),
],
border: isSelected
? Border.all(
color: Colors.white.withValues(alpha: 0.8),
width: 2,
)
? Border.all(color: Colors.white.withValues(alpha: 0.8), width: 2)
: Border.all(
color: Colors.white.withValues(alpha: 0.15),
width: 1,
@@ -474,4 +504,4 @@ class _AccountBalanceWidgetState extends State<AccountBalanceWidget> {
final formatter = NumberFormat('#,##0.00', 'en_US');
return '\$${formatter.format(balance)}';
}
}
}

View File

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

View File

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

View File

@@ -35,9 +35,9 @@ class GroupBatchItemUpdateRequest {
@JsonSerializable(explicitToJson: true)
class GroupBatchItemUpdateList {
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) =>
_$GroupBatchItemUpdateListFromJson(json);

View File

@@ -42,12 +42,12 @@ GroupBatchItemUpdateList _$GroupBatchItemUpdateListFromJson(
(e) => GroupBatchItemUpdateRequest.fromJson(e as Map<String, dynamic>),
)
.toList(),
userId: json['userId'] as String?,
workspaceId: json['workspaceId'] as String?,
);
Map<String, dynamic> _$GroupBatchItemUpdateListToJson(
GroupBatchItemUpdateList instance,
) => <String, dynamic>{
'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? recipientGroupId;
final String? userId;
final String? workspaceId;
final String? currency;
final String? description;
final double? value;
@@ -24,6 +25,7 @@ class GroupBatch {
this.id,
this.recipientGroupId,
this.userId,
this.workspaceId,
this.currency,
this.description,
this.value,
@@ -88,6 +90,7 @@ class GroupBatchItem {
class CreateBatchRequest {
final String recipientGroupId;
final String userId;
final String workspaceId;
final String currency;
final String description;
final double amount;
@@ -98,6 +101,7 @@ class CreateBatchRequest {
const CreateBatchRequest({
required this.recipientGroupId,
required this.userId,
required this.workspaceId,
required this.currency,
required this.description,
required this.amount,
@@ -114,12 +118,14 @@ class CreateBatchRequest {
@JsonSerializable()
class BatchActionRequest {
final String userId;
final String workspaceId;
final String idempotencyKey;
final String? authType;
const BatchActionRequest({
required this.userId,
required this.workspaceId,
required this.idempotencyKey,
this.authType,
});
factory BatchActionRequest.fromJson(Map<String, dynamic> json) =>
@@ -131,14 +137,14 @@ class BatchActionRequest {
@JsonSerializable(explicitToJson: true)
class GroupBatchUpdateRequest {
String? id;
String? userId;
String? workspaceId;
String? paymentProcessorLabel;
String? paymentProcessorName;
String? paymentProcessorImage;
GroupBatchUpdateRequest({
this.id,
this.userId,
this.workspaceId,
this.paymentProcessorLabel,
this.paymentProcessorName,
this.paymentProcessorImage,

View File

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

View File

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

View File

@@ -12,6 +12,7 @@ RecipientGroup _$RecipientGroupFromJson(Map<String, dynamic> json) =>
name: json['name'] as String,
description: json['description'] as String?,
userId: json['userId'] as String?,
workspaceId: json['workspaceId'] as String?,
createdAt: json['createdAt'] as String?,
);
@@ -21,6 +22,7 @@ Map<String, dynamic> _$RecipientGroupToJson(RecipientGroup instance) =>
'name': instance.name,
'description': instance.description,
'userId': instance.userId,
'workspaceId': instance.workspaceId,
'createdAt': instance.createdAt,
};
@@ -53,6 +55,7 @@ CreateGroupRequest _$CreateGroupRequestFromJson(Map<String, dynamic> json) =>
name: json['name'] as String,
description: json['description'] as String?,
userId: json['userId'] as String,
workspaceId: json['workspaceId'] as String,
recipients: (json['recipients'] as List<dynamic>)
.map((e) => Recipient.fromJson(e as Map<String, dynamic>))
.toList(),
@@ -63,5 +66,6 @@ Map<String, dynamic> _$CreateGroupRequestToJson(CreateGroupRequest instance) =>
'name': instance.name,
'description': instance.description,
'userId': instance.userId,
'workspaceId': instance.workspaceId,
'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 userId = prefs.getString('userId') ?? '';
final workspaceId = prefs.getString('workspaceId') ?? '';
final req = CreateBatchRequest(
recipientGroupId: widget.group.id ?? '',
userId: userId,
workspaceId: workspaceId,
currency: _currency,
description: _descriptionController.text.trim(),
amount: amount,
@@ -115,7 +117,7 @@ class _BatchCreateScreenState extends State<BatchCreateScreen> {
batch.id!,
selectedProvider,
selectedProduct,
userId,
workspaceId,
);
}
@@ -130,7 +132,7 @@ class _BatchCreateScreenState extends State<BatchCreateScreen> {
String batchId,
BillProvider provider,
BillProduct? product,
String userId,
String workspaceId,
) async {
// Fetch the batch items so we can update them
final itemsResult = await batchController.listBatchItems(batchId);
@@ -154,7 +156,7 @@ class _BatchCreateScreenState extends State<BatchCreateScreen> {
final updateList = GroupBatchItemUpdateList(
items: updateItems,
userId: userId,
workspaceId: workspaceId,
);
await batchController.updateBatchItems(batchId, updateList);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -102,10 +102,10 @@ class _LandingScreenState extends State<LandingScreen> {
),
const SizedBox(height: 8),
Center(
child: TextButton(
onPressed: () {
context.go('/');
},
child: TextButton(
onPressed: () {
context.go('/workspace');
},
child: Text(
'Continue as guest?',
style: TextStyle(

View File

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

View File

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

View File

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

View File

@@ -125,8 +125,12 @@ class ReceiptController extends ChangeNotifier {
Future<ApiResponse<TransactionModel>> getReceiptData(String id) async {
setLoading(true);
final prefs = await SharedPreferences.getInstance();
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);
model.transaction = transaction;
setLoading(false);

View File

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

View File

@@ -13,7 +13,7 @@ part of 'recipients_controller.dart';
T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Recipient {
mixin _$Recipient implements DiagnosticableTreeMixin {
@JsonKey(includeIfNull: false) String? get uid;@JsonKey(includeIfNull: false) String? get name;@JsonKey(includeIfNull: false) String? get email;@JsonKey(includeIfNull: false) String? get phoneNumber;@JsonKey(includeIfNull: false) String? get address;@JsonKey(includeIfNull: false) String? get account;@JsonKey(includeIfNull: false) String? get initials;@JsonKey(includeIfNull: false) String? get latestProviderLabel;
/// Create a copy of Recipient
@@ -25,6 +25,12 @@ $RecipientCopyWith<Recipient> get copyWith => _$RecipientCopyWithImpl<Recipient>
/// Serializes this Recipient to a JSON map.
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
bool operator ==(Object other) {
@@ -36,7 +42,7 @@ bool operator ==(Object other) {
int get hashCode => Object.hash(runtimeType,uid,name,email,phoneNumber,address,account,initials,latestProviderLabel);
@override
String toString() {
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'Recipient(uid: $uid, name: $name, email: $email, phoneNumber: $phoneNumber, address: $address, account: $account, initials: $initials, latestProviderLabel: $latestProviderLabel)';
}
@@ -215,7 +221,7 @@ return $default(_that.uid,_that.name,_that.email,_that.phoneNumber,_that.address
/// @nodoc
@JsonSerializable()
class _Recipient implements Recipient {
class _Recipient with DiagnosticableTreeMixin implements Recipient {
const _Recipient({@JsonKey(includeIfNull: false) this.uid, @JsonKey(includeIfNull: false) this.name, @JsonKey(includeIfNull: false) this.email, @JsonKey(includeIfNull: false) this.phoneNumber, @JsonKey(includeIfNull: false) this.address, @JsonKey(includeIfNull: false) this.account, @JsonKey(includeIfNull: false) this.initials, @JsonKey(includeIfNull: false) this.latestProviderLabel});
factory _Recipient.fromJson(Map<String, dynamic> json) => _$RecipientFromJson(json);
@@ -238,6 +244,12 @@ _$RecipientCopyWith<_Recipient> get copyWith => __$RecipientCopyWithImpl<_Recipi
Map<String, dynamic> toJson() {
return _$RecipientToJson(this, );
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
properties
..add(DiagnosticsProperty('type', 'Recipient'))
..add(DiagnosticsProperty('uid', uid))..add(DiagnosticsProperty('name', name))..add(DiagnosticsProperty('email', email))..add(DiagnosticsProperty('phoneNumber', phoneNumber))..add(DiagnosticsProperty('address', address))..add(DiagnosticsProperty('account', account))..add(DiagnosticsProperty('initials', initials))..add(DiagnosticsProperty('latestProviderLabel', latestProviderLabel));
}
@override
bool operator ==(Object other) {
@@ -249,7 +261,7 @@ bool operator ==(Object other) {
int get hashCode => Object.hash(runtimeType,uid,name,email,phoneNumber,address,account,initials,latestProviderLabel);
@override
String toString() {
String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) {
return 'Recipient(uid: $uid, name: $name, email: $email, phoneNumber: $phoneNumber, address: $address, account: $account, initials: $initials, latestProviderLabel: $latestProviderLabel)';
}

View File

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

View File

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

View File

@@ -16,7 +16,7 @@ T _$identity<T>(T value) => value;
mixin _$FormData {
String? get id; String get type;// CONFIRM, REQUEST, REVERSE
String get billClientId; String get debitRef; String get debitCurrency; String get amount; String get creditAccount; String? get creditPhone; String? get creditName; String? get creditEmail; String get billName; String? get errorMessage; String get status; String get paymentProcessorLabel; String get paymentProcessorName; String get paymentProcessorImage; String get providerImage; String get providerLabel; String get userId; String? get debitPhone; String? get debitAccount; String? get productUid; String? get billProductName; String? get trace; String? get authType; String? get charge; String? get gatewayCharge; String? get tax; String? get totalAmount; String? get region;
String get billClientId; String get debitRef; String get debitCurrency; String get amount; String get creditAccount; String? get creditPhone; String? get creditName; String? get creditEmail; String get billName; String? get errorMessage; String get status; String get paymentProcessorLabel; String get paymentProcessorName; String get paymentProcessorImage; String get providerImage; String get providerLabel; String get userId; String get workspaceId; String? get debitPhone; String? get debitAccount; String? get productUid; String? get billProductName; String? get trace; String? get authType; String? get charge; String? get gatewayCharge; String? get tax; String? get totalAmount; String? get region;
/// Create a copy of FormData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -29,16 +29,16 @@ $FormDataCopyWith<FormData> get copyWith => _$FormDataCopyWithImpl<FormData>(thi
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is FormData&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.billProductName, billProductName) || other.billProductName == billProductName)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)&&(identical(other.region, region) || other.region == region));
return identical(this, other) || (other.runtimeType == runtimeType&&other is FormData&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.billProductName, billProductName) || other.billProductName == billProductName)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)&&(identical(other.region, region) || other.region == region));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,id,type,billClientId,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,debitPhone,debitAccount,productUid,billProductName,trace,authType,charge,gatewayCharge,tax,totalAmount,region]);
int get hashCode => Object.hashAll([runtimeType,id,type,billClientId,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,workspaceId,debitPhone,debitAccount,productUid,billProductName,trace,authType,charge,gatewayCharge,tax,totalAmount,region]);
@override
String toString() {
return 'FormData(id: $id, type: $type, billClientId: $billClientId, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, billProductName: $billProductName, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount, region: $region)';
return 'FormData(id: $id, type: $type, billClientId: $billClientId, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, workspaceId: $workspaceId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, billProductName: $billProductName, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount, region: $region)';
}
@@ -49,7 +49,7 @@ abstract mixin class $FormDataCopyWith<$Res> {
factory $FormDataCopyWith(FormData value, $Res Function(FormData) _then) = _$FormDataCopyWithImpl;
@useResult
$Res call({
String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region
String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String workspaceId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region
});
@@ -66,7 +66,7 @@ class _$FormDataCopyWithImpl<$Res>
/// Create a copy of FormData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? type = null,Object? billClientId = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? billProductName = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,Object? region = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? id = freezed,Object? type = null,Object? billClientId = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? workspaceId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? billProductName = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,Object? region = freezed,}) {
return _then(_self.copyWith(
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
@@ -87,6 +87,7 @@ as String,paymentProcessorImage: null == paymentProcessorImage ? _self.paymentPr
as String,providerImage: null == providerImage ? _self.providerImage : providerImage // ignore: cast_nullable_to_non_nullable
as String,providerLabel: null == providerLabel ? _self.providerLabel : providerLabel // ignore: cast_nullable_to_non_nullable
as String,userId: null == userId ? _self.userId : userId // ignore: cast_nullable_to_non_nullable
as String,workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable
as String,debitPhone: freezed == debitPhone ? _self.debitPhone : debitPhone // ignore: cast_nullable_to_non_nullable
as String?,debitAccount: freezed == debitAccount ? _self.debitAccount : debitAccount // ignore: cast_nullable_to_non_nullable
as String?,productUid: freezed == productUid ? _self.productUid : productUid // ignore: cast_nullable_to_non_nullable
@@ -183,10 +184,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult 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) {
case _FormData() when $default != null:
return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debitCurrency,_that.amount,_that.creditAccount,_that.creditPhone,_that.creditName,_that.creditEmail,_that.billName,_that.errorMessage,_that.status,_that.paymentProcessorLabel,_that.paymentProcessorName,_that.paymentProcessorImage,_that.providerImage,_that.providerLabel,_that.userId,_that.debitPhone,_that.debitAccount,_that.productUid,_that.billProductName,_that.trace,_that.authType,_that.charge,_that.gatewayCharge,_that.tax,_that.totalAmount,_that.region);case _:
return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debitCurrency,_that.amount,_that.creditAccount,_that.creditPhone,_that.creditName,_that.creditEmail,_that.billName,_that.errorMessage,_that.status,_that.paymentProcessorLabel,_that.paymentProcessorName,_that.paymentProcessorImage,_that.providerImage,_that.providerLabel,_that.userId,_that.workspaceId,_that.debitPhone,_that.debitAccount,_that.productUid,_that.billProductName,_that.trace,_that.authType,_that.charge,_that.gatewayCharge,_that.tax,_that.totalAmount,_that.region);case _:
return orElse();
}
@@ -204,10 +205,10 @@ return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debi
/// }
/// ```
@optionalTypeArgs TResult when<TResult 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) {
case _FormData():
return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debitCurrency,_that.amount,_that.creditAccount,_that.creditPhone,_that.creditName,_that.creditEmail,_that.billName,_that.errorMessage,_that.status,_that.paymentProcessorLabel,_that.paymentProcessorName,_that.paymentProcessorImage,_that.providerImage,_that.providerLabel,_that.userId,_that.debitPhone,_that.debitAccount,_that.productUid,_that.billProductName,_that.trace,_that.authType,_that.charge,_that.gatewayCharge,_that.tax,_that.totalAmount,_that.region);case _:
return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debitCurrency,_that.amount,_that.creditAccount,_that.creditPhone,_that.creditName,_that.creditEmail,_that.billName,_that.errorMessage,_that.status,_that.paymentProcessorLabel,_that.paymentProcessorName,_that.paymentProcessorImage,_that.providerImage,_that.providerLabel,_that.userId,_that.workspaceId,_that.debitPhone,_that.debitAccount,_that.productUid,_that.billProductName,_that.trace,_that.authType,_that.charge,_that.gatewayCharge,_that.tax,_that.totalAmount,_that.region);case _:
throw StateError('Unexpected subclass');
}
@@ -224,10 +225,10 @@ return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debi
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult 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) {
case _FormData() when $default != null:
return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debitCurrency,_that.amount,_that.creditAccount,_that.creditPhone,_that.creditName,_that.creditEmail,_that.billName,_that.errorMessage,_that.status,_that.paymentProcessorLabel,_that.paymentProcessorName,_that.paymentProcessorImage,_that.providerImage,_that.providerLabel,_that.userId,_that.debitPhone,_that.debitAccount,_that.productUid,_that.billProductName,_that.trace,_that.authType,_that.charge,_that.gatewayCharge,_that.tax,_that.totalAmount,_that.region);case _:
return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debitCurrency,_that.amount,_that.creditAccount,_that.creditPhone,_that.creditName,_that.creditEmail,_that.billName,_that.errorMessage,_that.status,_that.paymentProcessorLabel,_that.paymentProcessorName,_that.paymentProcessorImage,_that.providerImage,_that.providerLabel,_that.userId,_that.workspaceId,_that.debitPhone,_that.debitAccount,_that.productUid,_that.billProductName,_that.trace,_that.authType,_that.charge,_that.gatewayCharge,_that.tax,_that.totalAmount,_that.region);case _:
return null;
}
@@ -239,7 +240,7 @@ return $default(_that.id,_that.type,_that.billClientId,_that.debitRef,_that.debi
@JsonSerializable()
class _FormData implements FormData {
const _FormData({this.id, required this.type, required this.billClientId, required this.debitRef, required this.debitCurrency, required this.amount, required this.creditAccount, required this.creditPhone, required this.creditName, required this.creditEmail, required this.billName, required this.errorMessage, required this.status, required this.paymentProcessorLabel, required this.paymentProcessorName, required this.paymentProcessorImage, required this.providerImage, required this.providerLabel, required this.userId, this.debitPhone, this.debitAccount, this.productUid, this.billProductName, this.trace, this.authType, this.charge, this.gatewayCharge, this.tax, this.totalAmount, this.region});
const _FormData({this.id, required this.type, required this.billClientId, required this.debitRef, required this.debitCurrency, required this.amount, required this.creditAccount, required this.creditPhone, required this.creditName, required this.creditEmail, required this.billName, required this.errorMessage, required this.status, required this.paymentProcessorLabel, required this.paymentProcessorName, required this.paymentProcessorImage, required this.providerImage, required this.providerLabel, required this.userId, required this.workspaceId, this.debitPhone, this.debitAccount, this.productUid, this.billProductName, this.trace, this.authType, this.charge, this.gatewayCharge, this.tax, this.totalAmount, this.region});
factory _FormData.fromJson(Map<String, dynamic> json) => _$FormDataFromJson(json);
@override final String? id;
@@ -262,6 +263,7 @@ class _FormData implements FormData {
@override final String providerImage;
@override final String providerLabel;
@override final String userId;
@override final String workspaceId;
@override final String? debitPhone;
@override final String? debitAccount;
@override final String? productUid;
@@ -287,16 +289,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _FormData&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.billProductName, billProductName) || other.billProductName == billProductName)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)&&(identical(other.region, region) || other.region == region));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _FormData&&(identical(other.id, id) || other.id == id)&&(identical(other.type, type) || other.type == type)&&(identical(other.billClientId, billClientId) || other.billClientId == billClientId)&&(identical(other.debitRef, debitRef) || other.debitRef == debitRef)&&(identical(other.debitCurrency, debitCurrency) || other.debitCurrency == debitCurrency)&&(identical(other.amount, amount) || other.amount == amount)&&(identical(other.creditAccount, creditAccount) || other.creditAccount == creditAccount)&&(identical(other.creditPhone, creditPhone) || other.creditPhone == creditPhone)&&(identical(other.creditName, creditName) || other.creditName == creditName)&&(identical(other.creditEmail, creditEmail) || other.creditEmail == creditEmail)&&(identical(other.billName, billName) || other.billName == billName)&&(identical(other.errorMessage, errorMessage) || other.errorMessage == errorMessage)&&(identical(other.status, status) || other.status == status)&&(identical(other.paymentProcessorLabel, paymentProcessorLabel) || other.paymentProcessorLabel == paymentProcessorLabel)&&(identical(other.paymentProcessorName, paymentProcessorName) || other.paymentProcessorName == paymentProcessorName)&&(identical(other.paymentProcessorImage, paymentProcessorImage) || other.paymentProcessorImage == paymentProcessorImage)&&(identical(other.providerImage, providerImage) || other.providerImage == providerImage)&&(identical(other.providerLabel, providerLabel) || other.providerLabel == providerLabel)&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.workspaceId, workspaceId) || other.workspaceId == workspaceId)&&(identical(other.debitPhone, debitPhone) || other.debitPhone == debitPhone)&&(identical(other.debitAccount, debitAccount) || other.debitAccount == debitAccount)&&(identical(other.productUid, productUid) || other.productUid == productUid)&&(identical(other.billProductName, billProductName) || other.billProductName == billProductName)&&(identical(other.trace, trace) || other.trace == trace)&&(identical(other.authType, authType) || other.authType == authType)&&(identical(other.charge, charge) || other.charge == charge)&&(identical(other.gatewayCharge, gatewayCharge) || other.gatewayCharge == gatewayCharge)&&(identical(other.tax, tax) || other.tax == tax)&&(identical(other.totalAmount, totalAmount) || other.totalAmount == totalAmount)&&(identical(other.region, region) || other.region == region));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hashAll([runtimeType,id,type,billClientId,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,debitPhone,debitAccount,productUid,billProductName,trace,authType,charge,gatewayCharge,tax,totalAmount,region]);
int get hashCode => Object.hashAll([runtimeType,id,type,billClientId,debitRef,debitCurrency,amount,creditAccount,creditPhone,creditName,creditEmail,billName,errorMessage,status,paymentProcessorLabel,paymentProcessorName,paymentProcessorImage,providerImage,providerLabel,userId,workspaceId,debitPhone,debitAccount,productUid,billProductName,trace,authType,charge,gatewayCharge,tax,totalAmount,region]);
@override
String toString() {
return 'FormData(id: $id, type: $type, billClientId: $billClientId, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, billProductName: $billProductName, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount, region: $region)';
return 'FormData(id: $id, type: $type, billClientId: $billClientId, debitRef: $debitRef, debitCurrency: $debitCurrency, amount: $amount, creditAccount: $creditAccount, creditPhone: $creditPhone, creditName: $creditName, creditEmail: $creditEmail, billName: $billName, errorMessage: $errorMessage, status: $status, paymentProcessorLabel: $paymentProcessorLabel, paymentProcessorName: $paymentProcessorName, paymentProcessorImage: $paymentProcessorImage, providerImage: $providerImage, providerLabel: $providerLabel, userId: $userId, workspaceId: $workspaceId, debitPhone: $debitPhone, debitAccount: $debitAccount, productUid: $productUid, billProductName: $billProductName, trace: $trace, authType: $authType, charge: $charge, gatewayCharge: $gatewayCharge, tax: $tax, totalAmount: $totalAmount, region: $region)';
}
@@ -307,7 +309,7 @@ abstract mixin class _$FormDataCopyWith<$Res> implements $FormDataCopyWith<$Res>
factory _$FormDataCopyWith(_FormData value, $Res Function(_FormData) _then) = __$FormDataCopyWithImpl;
@override @useResult
$Res call({
String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region
String? id, String type, String billClientId, String debitRef, String debitCurrency, String amount, String creditAccount, String? creditPhone, String? creditName, String? creditEmail, String billName, String? errorMessage, String status, String paymentProcessorLabel, String paymentProcessorName, String paymentProcessorImage, String providerImage, String providerLabel, String userId, String workspaceId, String? debitPhone, String? debitAccount, String? productUid, String? billProductName, String? trace, String? authType, String? charge, String? gatewayCharge, String? tax, String? totalAmount, String? region
});
@@ -324,7 +326,7 @@ class __$FormDataCopyWithImpl<$Res>
/// Create a copy of FormData
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? type = null,Object? billClientId = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? billProductName = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,Object? region = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? id = freezed,Object? type = null,Object? billClientId = null,Object? debitRef = null,Object? debitCurrency = null,Object? amount = null,Object? creditAccount = null,Object? creditPhone = freezed,Object? creditName = freezed,Object? creditEmail = freezed,Object? billName = null,Object? errorMessage = freezed,Object? status = null,Object? paymentProcessorLabel = null,Object? paymentProcessorName = null,Object? paymentProcessorImage = null,Object? providerImage = null,Object? providerLabel = null,Object? userId = null,Object? workspaceId = null,Object? debitPhone = freezed,Object? debitAccount = freezed,Object? productUid = freezed,Object? billProductName = freezed,Object? trace = freezed,Object? authType = freezed,Object? charge = freezed,Object? gatewayCharge = freezed,Object? tax = freezed,Object? totalAmount = freezed,Object? region = freezed,}) {
return _then(_FormData(
id: freezed == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
@@ -345,6 +347,7 @@ as String,paymentProcessorImage: null == paymentProcessorImage ? _self.paymentPr
as String,providerImage: null == providerImage ? _self.providerImage : providerImage // ignore: cast_nullable_to_non_nullable
as String,providerLabel: null == providerLabel ? _self.providerLabel : providerLabel // ignore: cast_nullable_to_non_nullable
as String,userId: null == userId ? _self.userId : userId // ignore: cast_nullable_to_non_nullable
as String,workspaceId: null == workspaceId ? _self.workspaceId : workspaceId // ignore: cast_nullable_to_non_nullable
as String,debitPhone: freezed == debitPhone ? _self.debitPhone : debitPhone // ignore: cast_nullable_to_non_nullable
as String?,debitAccount: freezed == debitAccount ? _self.debitAccount : debitAccount // ignore: cast_nullable_to_non_nullable
as String?,productUid: freezed == productUid ? _self.productUid : productUid // ignore: cast_nullable_to_non_nullable

View File

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