feat: update profile setup and bank account management; enhance API integration and data handling
This commit is contained in:
@@ -61,7 +61,7 @@ class StaffSession extends Equatable {
|
|||||||
vendorId: staff['vendorId'] as String?,
|
vendorId: staff['vendorId'] as String?,
|
||||||
workforceNumber: staff['workforceNumber'] as String?,
|
workforceNumber: staff['workforceNumber'] as String?,
|
||||||
metadata: (staff['metadata'] as Map<String, dynamic>?) ?? const <String, dynamic>{},
|
metadata: (staff['metadata'] as Map<String, dynamic>?) ?? const <String, dynamic>{},
|
||||||
userId: user['userId'] as String?,
|
userId: (user['userId'] ?? user['id']) as String?,
|
||||||
tenantName: tenant['tenantName'] as String?,
|
tenantName: tenant['tenantName'] as String?,
|
||||||
tenantSlug: tenant['tenantSlug'] as String?,
|
tenantSlug: tenant['tenantSlug'] as String?,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
@@ -20,18 +21,32 @@ class ProfileSetupRepositoryImpl implements ProfileSetupRepository {
|
|||||||
@override
|
@override
|
||||||
Future<void> submitProfile({
|
Future<void> submitProfile({
|
||||||
required String fullName,
|
required String fullName,
|
||||||
|
required String phoneNumber,
|
||||||
String? bio,
|
String? bio,
|
||||||
required List<String> preferredLocations,
|
required List<String> preferredLocations,
|
||||||
required double maxDistanceMiles,
|
required double maxDistanceMiles,
|
||||||
required List<String> industries,
|
required List<String> industries,
|
||||||
required List<String> skills,
|
required List<String> skills,
|
||||||
}) async {
|
}) async {
|
||||||
|
// Convert location label strings to the object shape the V2 API expects.
|
||||||
|
// The backend zod schema requires: { label, city?, state?, ... }.
|
||||||
|
final List<Map<String, String>> locationObjects = preferredLocations
|
||||||
|
.map((String label) => <String, String>{'label': label})
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// Resolve the phone number: prefer the explicit parameter, but fall back
|
||||||
|
// to the Firebase Auth current user's phone if the caller passed empty.
|
||||||
|
final String resolvedPhone = phoneNumber.isNotEmpty
|
||||||
|
? phoneNumber
|
||||||
|
: (FirebaseAuth.instance.currentUser?.phoneNumber ?? '');
|
||||||
|
|
||||||
final ApiResponse response = await _apiService.post(
|
final ApiResponse response = await _apiService.post(
|
||||||
StaffEndpoints.profileSetup,
|
StaffEndpoints.profileSetup,
|
||||||
data: <String, dynamic>{
|
data: <String, dynamic>{
|
||||||
'fullName': fullName,
|
'fullName': fullName,
|
||||||
|
'phoneNumber': resolvedPhone,
|
||||||
if (bio != null && bio.isNotEmpty) 'bio': bio,
|
if (bio != null && bio.isNotEmpty) 'bio': bio,
|
||||||
'preferredLocations': preferredLocations,
|
'preferredLocations': locationObjects,
|
||||||
'maxDistanceMiles': maxDistanceMiles.toInt(),
|
'maxDistanceMiles': maxDistanceMiles.toInt(),
|
||||||
'industries': industries,
|
'industries': industries,
|
||||||
'skills': skills,
|
'skills': skills,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
|
/// Interface for the staff profile setup repository.
|
||||||
abstract class ProfileSetupRepository {
|
abstract class ProfileSetupRepository {
|
||||||
|
/// Submits the staff profile setup data to the backend.
|
||||||
Future<void> submitProfile({
|
Future<void> submitProfile({
|
||||||
required String fullName,
|
required String fullName,
|
||||||
|
required String phoneNumber,
|
||||||
String? bio,
|
String? bio,
|
||||||
required List<String> preferredLocations,
|
required List<String> preferredLocations,
|
||||||
required double maxDistanceMiles,
|
required double maxDistanceMiles,
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class SubmitProfileSetup {
|
|||||||
/// Submits the profile setup with the given data.
|
/// Submits the profile setup with the given data.
|
||||||
Future<void> call({
|
Future<void> call({
|
||||||
required String fullName,
|
required String fullName,
|
||||||
|
required String phoneNumber,
|
||||||
String? bio,
|
String? bio,
|
||||||
required List<String> preferredLocations,
|
required List<String> preferredLocations,
|
||||||
required double maxDistanceMiles,
|
required double maxDistanceMiles,
|
||||||
@@ -21,6 +22,7 @@ class SubmitProfileSetup {
|
|||||||
}) {
|
}) {
|
||||||
return repository.submitProfile(
|
return repository.submitProfile(
|
||||||
fullName: fullName,
|
fullName: fullName,
|
||||||
|
phoneNumber: phoneNumber,
|
||||||
bio: bio,
|
bio: bio,
|
||||||
preferredLocations: preferredLocations,
|
preferredLocations: preferredLocations,
|
||||||
maxDistanceMiles: maxDistanceMiles,
|
maxDistanceMiles: maxDistanceMiles,
|
||||||
|
|||||||
@@ -12,11 +12,16 @@ export 'package:staff_authentication/src/presentation/blocs/profile_setup/profil
|
|||||||
class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
|
class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
|
||||||
with BlocErrorHandler<ProfileSetupState> {
|
with BlocErrorHandler<ProfileSetupState> {
|
||||||
/// Creates a [ProfileSetupBloc].
|
/// Creates a [ProfileSetupBloc].
|
||||||
|
///
|
||||||
|
/// [phoneNumber] is the authenticated user's phone from the sign-up flow,
|
||||||
|
/// required by the V2 profile-setup endpoint.
|
||||||
ProfileSetupBloc({
|
ProfileSetupBloc({
|
||||||
required SubmitProfileSetup submitProfileSetup,
|
required SubmitProfileSetup submitProfileSetup,
|
||||||
required SearchCitiesUseCase searchCities,
|
required SearchCitiesUseCase searchCities,
|
||||||
|
required String phoneNumber,
|
||||||
}) : _submitProfileSetup = submitProfileSetup,
|
}) : _submitProfileSetup = submitProfileSetup,
|
||||||
_searchCities = searchCities,
|
_searchCities = searchCities,
|
||||||
|
_phoneNumber = phoneNumber,
|
||||||
super(const ProfileSetupState()) {
|
super(const ProfileSetupState()) {
|
||||||
on<ProfileSetupFullNameChanged>(_onFullNameChanged);
|
on<ProfileSetupFullNameChanged>(_onFullNameChanged);
|
||||||
on<ProfileSetupBioChanged>(_onBioChanged);
|
on<ProfileSetupBioChanged>(_onBioChanged);
|
||||||
@@ -35,6 +40,9 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
|
|||||||
/// The use case for searching cities.
|
/// The use case for searching cities.
|
||||||
final SearchCitiesUseCase _searchCities;
|
final SearchCitiesUseCase _searchCities;
|
||||||
|
|
||||||
|
/// The user's phone number from the sign-up flow.
|
||||||
|
final String _phoneNumber;
|
||||||
|
|
||||||
/// Handles the [ProfileSetupFullNameChanged] event.
|
/// Handles the [ProfileSetupFullNameChanged] event.
|
||||||
void _onFullNameChanged(
|
void _onFullNameChanged(
|
||||||
ProfileSetupFullNameChanged event,
|
ProfileSetupFullNameChanged event,
|
||||||
@@ -95,6 +103,7 @@ class ProfileSetupBloc extends Bloc<ProfileSetupEvent, ProfileSetupState>
|
|||||||
action: () async {
|
action: () async {
|
||||||
await _submitProfileSetup(
|
await _submitProfileSetup(
|
||||||
fullName: state.fullName,
|
fullName: state.fullName,
|
||||||
|
phoneNumber: _phoneNumber,
|
||||||
bio: state.bio.isEmpty ? null : state.bio,
|
bio: state.bio.isEmpty ? null : state.bio,
|
||||||
preferredLocations: state.preferredLocations,
|
preferredLocations: state.preferredLocations,
|
||||||
maxDistanceMiles: state.maxDistanceMiles,
|
maxDistanceMiles: state.maxDistanceMiles,
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class StaffAuthenticationModule extends Module {
|
|||||||
() => ProfileSetupBloc(
|
() => ProfileSetupBloc(
|
||||||
submitProfileSetup: i.get<SubmitProfileSetup>(),
|
submitProfileSetup: i.get<SubmitProfileSetup>(),
|
||||||
searchCities: i.get<SearchCitiesUseCase>(),
|
searchCities: i.get<SearchCitiesUseCase>(),
|
||||||
|
phoneNumber: i.get<AuthBloc>().state.phoneNumber,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ class ClockInRepositoryImpl implements ClockInRepositoryInterface {
|
|||||||
id: json['shiftId'] as String,
|
id: json['shiftId'] as String,
|
||||||
orderId: json['orderId'] as String? ?? '',
|
orderId: json['orderId'] as String? ?? '',
|
||||||
title: json['clientName'] as String? ?? json['roleName'] as String? ?? '',
|
title: json['clientName'] as String? ?? json['roleName'] as String? ?? '',
|
||||||
status: ShiftStatus.fromJson(json['attendanceStatus'] as String?),
|
status: ShiftStatus.assigned,
|
||||||
startsAt: DateTime.parse(json['startTime'] as String),
|
startsAt: DateTime.parse(json['startTime'] as String),
|
||||||
endsAt: DateTime.parse(json['endTime'] as String),
|
endsAt: DateTime.parse(json['endTime'] as String),
|
||||||
locationName: json['location'] as String?,
|
locationName: json['location'] as String?,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class CertificatesRepositoryImpl implements CertificatesRepository {
|
|||||||
final ApiResponse response =
|
final ApiResponse response =
|
||||||
await _api.get(StaffEndpoints.certificates);
|
await _api.get(StaffEndpoints.certificates);
|
||||||
final List<dynamic> items =
|
final List<dynamic> items =
|
||||||
response.data['certificates'] as List<dynamic>? ?? <dynamic>[];
|
response.data['items'] as List<dynamic>? ?? <dynamic>[];
|
||||||
return items
|
return items
|
||||||
.map((dynamic json) =>
|
.map((dynamic json) =>
|
||||||
StaffCertificate.fromJson(json as Map<String, dynamic>))
|
StaffCertificate.fromJson(json as Map<String, dynamic>))
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ class DocumentsRepositoryImpl implements DocumentsRepository {
|
|||||||
Future<List<ProfileDocument>> getDocuments() async {
|
Future<List<ProfileDocument>> getDocuments() async {
|
||||||
final ApiResponse response =
|
final ApiResponse response =
|
||||||
await _api.get(StaffEndpoints.documents);
|
await _api.get(StaffEndpoints.documents);
|
||||||
final List<dynamic> items = response.data['documents'] as List<dynamic>? ?? <dynamic>[];
|
final List<dynamic> items = response.data['items'] as List<dynamic>? ?? <dynamic>[];
|
||||||
return items
|
return items
|
||||||
.map((dynamic json) =>
|
.map((dynamic json) =>
|
||||||
ProfileDocument.fromJson(json as Map<String, dynamic>))
|
ProfileDocument.fromJson(json as Map<String, dynamic>))
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ class TaxFormsRepositoryImpl implements TaxFormsRepository {
|
|||||||
Future<List<TaxForm>> getTaxForms() async {
|
Future<List<TaxForm>> getTaxForms() async {
|
||||||
final ApiResponse response =
|
final ApiResponse response =
|
||||||
await _api.get(StaffEndpoints.taxForms);
|
await _api.get(StaffEndpoints.taxForms);
|
||||||
final List<dynamic> items = response.data['taxForms'] as List<dynamic>? ?? <dynamic>[];
|
final List<dynamic> items = response.data['items'] as List<dynamic>? ?? <dynamic>[];
|
||||||
return items
|
return items
|
||||||
.map((dynamic json) =>
|
.map((dynamic json) =>
|
||||||
TaxForm.fromJson(json as Map<String, dynamic>))
|
TaxForm.fromJson(json as Map<String, dynamic>))
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
|
import 'package:staff_bank_account/src/domain/arguments/add_bank_account_params.dart';
|
||||||
import 'package:staff_bank_account/src/domain/repositories/bank_account_repository.dart';
|
import 'package:staff_bank_account/src/domain/repositories/bank_account_repository.dart';
|
||||||
|
|
||||||
/// Implementation of [BankAccountRepository] using the V2 API.
|
/// Implementation of [BankAccountRepository] using the V2 API.
|
||||||
@@ -17,7 +18,7 @@ class BankAccountRepositoryImpl implements BankAccountRepository {
|
|||||||
Future<List<BankAccount>> getAccounts() async {
|
Future<List<BankAccount>> getAccounts() async {
|
||||||
final ApiResponse response =
|
final ApiResponse response =
|
||||||
await _api.get(StaffEndpoints.bankAccounts);
|
await _api.get(StaffEndpoints.bankAccounts);
|
||||||
final List<dynamic> items = response.data['accounts'] as List<dynamic>? ?? <dynamic>[];
|
final List<dynamic> items = response.data['items'] as List<dynamic>? ?? <dynamic>[];
|
||||||
return items
|
return items
|
||||||
.map((dynamic json) =>
|
.map((dynamic json) =>
|
||||||
BankAccount.fromJson(json as Map<String, dynamic>))
|
BankAccount.fromJson(json as Map<String, dynamic>))
|
||||||
@@ -25,10 +26,10 @@ class BankAccountRepositoryImpl implements BankAccountRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> addAccount(BankAccount account) async {
|
Future<void> addAccount(AddBankAccountParams params) async {
|
||||||
await _api.post(
|
await _api.post(
|
||||||
StaffEndpoints.bankAccounts,
|
StaffEndpoints.bankAccounts,
|
||||||
data: account.toJson(),
|
data: params.toJson(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,44 @@
|
|||||||
import 'package:equatable/equatable.dart';
|
|
||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart' show AccountType;
|
||||||
|
|
||||||
/// Arguments for adding a bank account.
|
/// Parameters for creating a new bank account via the V2 API.
|
||||||
class AddBankAccountParams extends UseCaseArgument with EquatableMixin {
|
///
|
||||||
|
/// Maps directly to the `bankAccountCreateSchema` zod schema:
|
||||||
|
/// `{ bankName, accountNumber, routingNumber, accountType }`.
|
||||||
|
class AddBankAccountParams extends UseCaseArgument {
|
||||||
|
/// Creates an [AddBankAccountParams].
|
||||||
|
const AddBankAccountParams({
|
||||||
|
required this.bankName,
|
||||||
|
required this.accountNumber,
|
||||||
|
required this.routingNumber,
|
||||||
|
required this.accountType,
|
||||||
|
});
|
||||||
|
|
||||||
const AddBankAccountParams({required this.account});
|
/// Name of the bank / financial institution.
|
||||||
final BankAccount account;
|
final String bankName;
|
||||||
|
|
||||||
|
/// Full account number.
|
||||||
|
final String accountNumber;
|
||||||
|
|
||||||
|
/// Routing / transit number.
|
||||||
|
final String routingNumber;
|
||||||
|
|
||||||
|
/// Account type (checking or savings).
|
||||||
|
final AccountType accountType;
|
||||||
|
|
||||||
|
/// Serialises to the V2 API request body.
|
||||||
|
Map<String, dynamic> toJson() => <String, dynamic>{
|
||||||
|
'bankName': bankName,
|
||||||
|
'accountNumber': accountNumber,
|
||||||
|
'routingNumber': routingNumber,
|
||||||
|
'accountType': accountType.toJson(),
|
||||||
|
};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => <Object?>[account];
|
List<Object?> get props => <Object?>[
|
||||||
|
bankName,
|
||||||
@override
|
accountNumber,
|
||||||
bool? get stringify => true;
|
routingNumber,
|
||||||
|
accountType,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart' show BankAccount;
|
||||||
|
|
||||||
|
import '../arguments/add_bank_account_params.dart';
|
||||||
|
|
||||||
/// Repository interface for managing bank accounts.
|
/// Repository interface for managing bank accounts.
|
||||||
///
|
|
||||||
/// Uses [BankAccount] from the V2 domain layer.
|
|
||||||
abstract class BankAccountRepository {
|
abstract class BankAccountRepository {
|
||||||
/// Fetches the list of bank accounts for the current staff member.
|
/// Fetches the list of bank accounts for the current staff member.
|
||||||
Future<List<BankAccount>> getAccounts();
|
Future<List<BankAccount>> getAccounts();
|
||||||
|
|
||||||
/// Adds a new bank account.
|
/// Creates a new bank account with the given [params].
|
||||||
Future<void> addAccount(BankAccount account);
|
Future<void> addAccount(AddBankAccountParams params);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
import '../repositories/bank_account_repository.dart';
|
|
||||||
import '../arguments/add_bank_account_params.dart';
|
import '../arguments/add_bank_account_params.dart';
|
||||||
|
import '../repositories/bank_account_repository.dart';
|
||||||
|
|
||||||
/// Use case to add a bank account.
|
/// Use case to add a bank account.
|
||||||
class AddBankAccountUseCase implements UseCase<AddBankAccountParams, void> {
|
class AddBankAccountUseCase implements UseCase<AddBankAccountParams, void> {
|
||||||
|
/// Creates an [AddBankAccountUseCase].
|
||||||
AddBankAccountUseCase(this._repository);
|
AddBankAccountUseCase(this._repository);
|
||||||
|
|
||||||
final BankAccountRepository _repository;
|
final BankAccountRepository _repository;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> call(AddBankAccountParams params) {
|
Future<void> call(AddBankAccountParams params) {
|
||||||
return _repository.addAccount(params.account);
|
return _repository.addAccount(params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart' show AccountType, BankAccount;
|
||||||
|
|
||||||
import '../../domain/arguments/add_bank_account_params.dart';
|
import '../../domain/arguments/add_bank_account_params.dart';
|
||||||
import '../../domain/usecases/add_bank_account_usecase.dart';
|
import '../../domain/usecases/add_bank_account_usecase.dart';
|
||||||
import '../../domain/usecases/get_bank_accounts_usecase.dart';
|
import '../../domain/usecases/get_bank_accounts_usecase.dart';
|
||||||
@@ -47,15 +48,10 @@ class BankAccountCubit extends Cubit<BankAccountState>
|
|||||||
}) async {
|
}) async {
|
||||||
emit(state.copyWith(status: BankAccountStatus.loading));
|
emit(state.copyWith(status: BankAccountStatus.loading));
|
||||||
|
|
||||||
// Create domain entity
|
final AddBankAccountParams params = AddBankAccountParams(
|
||||||
final BankAccount newAccount = BankAccount(
|
|
||||||
accountId: '', // Generated by server
|
|
||||||
bankName: bankName,
|
bankName: bankName,
|
||||||
providerReference: routingNumber,
|
accountNumber: accountNumber,
|
||||||
last4: accountNumber.length > 4
|
routingNumber: routingNumber,
|
||||||
? accountNumber.substring(accountNumber.length - 4)
|
|
||||||
: accountNumber,
|
|
||||||
isPrimary: false,
|
|
||||||
accountType: type == 'CHECKING'
|
accountType: type == 'CHECKING'
|
||||||
? AccountType.checking
|
? AccountType.checking
|
||||||
: AccountType.savings,
|
: AccountType.savings,
|
||||||
@@ -64,7 +60,7 @@ class BankAccountCubit extends Cubit<BankAccountState>
|
|||||||
await handleError(
|
await handleError(
|
||||||
emit: emit,
|
emit: emit,
|
||||||
action: () async {
|
action: () async {
|
||||||
await _addBankAccountUseCase(AddBankAccountParams(account: newAccount));
|
await _addBankAccountUseCase(params);
|
||||||
|
|
||||||
// Re-fetch to get latest state including server-generated IDs
|
// Re-fetch to get latest state including server-generated IDs
|
||||||
await loadAccounts();
|
await loadAccounts();
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ class TimeCardRepositoryImpl implements TimeCardRepository {
|
|||||||
'month': month.month,
|
'month': month.month,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
final List<dynamic> items = response.data['entries'] as List<dynamic>? ?? <dynamic>[];
|
final List<dynamic> items = response.data['items'] as List<dynamic>? ?? <dynamic>[];
|
||||||
return items
|
return items
|
||||||
.map((dynamic json) =>
|
.map((dynamic json) =>
|
||||||
TimeCardEntry.fromJson(json as Map<String, dynamic>))
|
TimeCardEntry.fromJson(json as Map<String, dynamic>))
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ class EmergencyContactRepositoryImpl
|
|||||||
Future<List<EmergencyContact>> getContacts() async {
|
Future<List<EmergencyContact>> getContacts() async {
|
||||||
final ApiResponse response =
|
final ApiResponse response =
|
||||||
await _api.get(StaffEndpoints.emergencyContacts);
|
await _api.get(StaffEndpoints.emergencyContacts);
|
||||||
final List<dynamic> items = response.data['contacts'] as List<dynamic>? ?? <dynamic>[];
|
final List<dynamic> items =
|
||||||
|
response.data['items'] as List<dynamic>? ?? <dynamic>[];
|
||||||
return items
|
return items
|
||||||
.map((dynamic json) =>
|
.map((dynamic json) =>
|
||||||
EmergencyContact.fromJson(json as Map<String, dynamic>))
|
EmergencyContact.fromJson(json as Map<String, dynamic>))
|
||||||
@@ -27,12 +28,27 @@ class EmergencyContactRepositoryImpl
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> saveContacts(List<EmergencyContact> contacts) async {
|
Future<void> saveContacts(List<EmergencyContact> contacts) async {
|
||||||
await _api.post(
|
for (final EmergencyContact contact in contacts) {
|
||||||
StaffEndpoints.emergencyContacts,
|
final Map<String, dynamic> body = <String, dynamic>{
|
||||||
data: <String, dynamic>{
|
'fullName': contact.fullName,
|
||||||
'contacts':
|
'phone': contact.phone,
|
||||||
contacts.map((EmergencyContact c) => c.toJson()).toList(),
|
'relationshipType': contact.relationshipType,
|
||||||
},
|
'isPrimary': contact.isPrimary,
|
||||||
);
|
};
|
||||||
|
|
||||||
|
if (contact.contactId.isNotEmpty) {
|
||||||
|
// Existing contact — update via PUT.
|
||||||
|
await _api.put(
|
||||||
|
StaffEndpoints.emergencyContactUpdate(contact.contactId),
|
||||||
|
data: body,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// New contact — create via POST.
|
||||||
|
await _api.post(
|
||||||
|
StaffEndpoints.emergencyContacts,
|
||||||
|
data: body,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:dio/dio.dart';
|
||||||
import 'package:krow_core/core.dart';
|
import 'package:krow_core/core.dart';
|
||||||
import 'package:krow_domain/krow_domain.dart';
|
import 'package:krow_domain/krow_domain.dart';
|
||||||
|
|
||||||
@@ -10,20 +11,12 @@ import 'package:staff_profile_info/src/domain/repositories/personal_info_reposit
|
|||||||
class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
|
class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
|
||||||
/// Creates a [PersonalInfoRepositoryImpl].
|
/// Creates a [PersonalInfoRepositoryImpl].
|
||||||
///
|
///
|
||||||
/// Requires the V2 [BaseApiService] for HTTP communication,
|
/// Requires the V2 [BaseApiService] for HTTP communication.
|
||||||
/// [FileUploadService] for uploading files to cloud storage, and
|
|
||||||
/// [SignedUrlService] for generating signed download URLs.
|
|
||||||
PersonalInfoRepositoryImpl({
|
PersonalInfoRepositoryImpl({
|
||||||
required BaseApiService apiService,
|
required BaseApiService apiService,
|
||||||
required FileUploadService uploadService,
|
}) : _api = apiService;
|
||||||
required SignedUrlService signedUrlService,
|
|
||||||
}) : _api = apiService,
|
|
||||||
_uploadService = uploadService,
|
|
||||||
_signedUrlService = signedUrlService;
|
|
||||||
|
|
||||||
final BaseApiService _api;
|
final BaseApiService _api;
|
||||||
final FileUploadService _uploadService;
|
|
||||||
final SignedUrlService _signedUrlService;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<StaffPersonalInfo> getStaffProfile() async {
|
Future<StaffPersonalInfo> getStaffProfile() async {
|
||||||
@@ -39,39 +32,34 @@ class PersonalInfoRepositoryImpl implements PersonalInfoRepositoryInterface {
|
|||||||
required String staffId,
|
required String staffId,
|
||||||
required Map<String, dynamic> data,
|
required Map<String, dynamic> data,
|
||||||
}) async {
|
}) async {
|
||||||
final ApiResponse response = await _api.put(
|
// The PUT response returns { staffId, fullName, email, phone, metadata }
|
||||||
|
// which does not match the StaffPersonalInfo shape. Perform the update
|
||||||
|
// and then re-fetch the full profile to return the correct entity.
|
||||||
|
await _api.put(
|
||||||
StaffEndpoints.personalInfo,
|
StaffEndpoints.personalInfo,
|
||||||
data: data,
|
data: data,
|
||||||
);
|
);
|
||||||
final Map<String, dynamic> json =
|
return getStaffProfile();
|
||||||
response.data as Map<String, dynamic>;
|
|
||||||
return StaffPersonalInfo.fromJson(json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> uploadProfilePhoto(String filePath) async {
|
Future<String> uploadProfilePhoto(String filePath) async {
|
||||||
// 1. Upload the file to cloud storage.
|
// The backend expects a multipart file upload at /staff/profile/photo.
|
||||||
final FileUploadResponse uploadRes = await _uploadService.uploadFile(
|
// It uploads to GCS, updates staff metadata, and returns a signed URL.
|
||||||
filePath: filePath,
|
final String fileName =
|
||||||
fileName:
|
'staff_profile_photo_${DateTime.now().millisecondsSinceEpoch}.jpg';
|
||||||
'staff_profile_photo_${DateTime.now().millisecondsSinceEpoch}.jpg',
|
final FormData formData = FormData.fromMap(<String, dynamic>{
|
||||||
visibility: FileVisibility.public,
|
'file': await MultipartFile.fromFile(filePath, filename: fileName),
|
||||||
);
|
});
|
||||||
|
|
||||||
// 2. Generate a signed URL for the uploaded file.
|
final ApiResponse response = await _api.post(
|
||||||
final SignedUrlResponse signedUrlRes =
|
|
||||||
await _signedUrlService.createSignedUrl(fileUri: uploadRes.fileUri);
|
|
||||||
final String photoUrl = signedUrlRes.signedUrl;
|
|
||||||
|
|
||||||
// 3. Submit the photo URL to the V2 API.
|
|
||||||
await _api.post(
|
|
||||||
StaffEndpoints.profilePhoto,
|
StaffEndpoints.profilePhoto,
|
||||||
data: <String, dynamic>{
|
data: formData,
|
||||||
'fileUri': uploadRes.fileUri,
|
|
||||||
'photoUrl': photoUrl,
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
final Map<String, dynamic> json =
|
||||||
|
response.data as Map<String, dynamic>;
|
||||||
|
|
||||||
return photoUrl;
|
// Backend returns { staffId, fileUri, signedUrl, expiresAt }.
|
||||||
|
return json['signedUrl'] as String? ?? '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ class StaffProfileInfoModule extends Module {
|
|||||||
i.addLazySingleton<PersonalInfoRepositoryInterface>(
|
i.addLazySingleton<PersonalInfoRepositoryInterface>(
|
||||||
() => PersonalInfoRepositoryImpl(
|
() => PersonalInfoRepositoryImpl(
|
||||||
apiService: i.get<BaseApiService>(),
|
apiService: i.get<BaseApiService>(),
|
||||||
uploadService: i.get<FileUploadService>(),
|
|
||||||
signedUrlService: i.get<SignedUrlService>(),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ dependencies:
|
|||||||
flutter_bloc: ^8.1.0
|
flutter_bloc: ^8.1.0
|
||||||
bloc: ^8.1.0
|
bloc: ^8.1.0
|
||||||
flutter_modular: ^6.3.0
|
flutter_modular: ^6.3.0
|
||||||
|
dio: ^5.9.1
|
||||||
equatable: ^2.0.5
|
equatable: ^2.0.5
|
||||||
|
|
||||||
# Architecture Packages
|
# Architecture Packages
|
||||||
|
|||||||
@@ -96,12 +96,10 @@ class ShiftsRepositoryImpl implements ShiftsRepositoryInterface {
|
|||||||
final ApiResponse response =
|
final ApiResponse response =
|
||||||
await _apiService.get(StaffEndpoints.shiftsCompleted);
|
await _apiService.get(StaffEndpoints.shiftsCompleted);
|
||||||
final List<dynamic> items = _extractItems(response.data);
|
final List<dynamic> items = _extractItems(response.data);
|
||||||
var x = items
|
return items
|
||||||
.map((dynamic json) =>
|
.map((dynamic json) =>
|
||||||
CompletedShift.fromJson(json as Map<String, dynamic>))
|
CompletedShift.fromJson(json as Map<String, dynamic>))
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
return x;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
Reference in New Issue
Block a user