feat: Refactor W-4 form page to use BLoC pattern and implement form submission logic
- Updated FormW4Page to utilize FormW4Cubit for state management. - Removed local state management and replaced with BLoC state. - Implemented form validation logic based on BLoC state. - Added methods for handling form field updates and navigation between steps. - Integrated success and error handling for form submission. - Refactored UI components to reflect changes in state management. feat: Update TaxFormsPage to use TaxFormsCubit for loading tax forms - Changed the way TaxFormsPage retrieves the TaxFormsCubit instance. - Ensured that tax forms are loaded on initial build. feat: Replace mock repository with implementation for tax forms - Switched from TaxFormsRepositoryMock to TaxFormsRepositoryImpl. - Added use cases for getting and submitting tax forms. - Implemented the TaxFormsRepositoryImpl to handle data fetching and submission logic. feat: Implement use cases for tax forms - Created GetTaxFormsUseCase for retrieving tax forms. - Created SubmitTaxFormUseCase for submitting tax form data. - Ensured use cases interact with the repository for data operations.
This commit is contained in:
@@ -0,0 +1,206 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:firebase_auth/firebase_auth.dart' hide User;
|
||||||
|
import 'package:firebase_data_connect/firebase_data_connect.dart';
|
||||||
|
import 'package:krow_data_connect/krow_data_connect.dart' as dc;
|
||||||
|
|
||||||
|
import '../../domain/entities/tax_form_entity.dart';
|
||||||
|
import '../../domain/repositories/tax_forms_repository.dart';
|
||||||
|
|
||||||
|
class TaxFormsRepositoryImpl implements TaxFormsRepository {
|
||||||
|
TaxFormsRepositoryImpl({
|
||||||
|
required this.firebaseAuth,
|
||||||
|
required this.dataConnect,
|
||||||
|
});
|
||||||
|
|
||||||
|
final FirebaseAuth firebaseAuth;
|
||||||
|
final dc.ExampleConnector dataConnect;
|
||||||
|
|
||||||
|
String? _staffId;
|
||||||
|
|
||||||
|
Future<String> _getStaffId() async {
|
||||||
|
if (_staffId != null) return _staffId!;
|
||||||
|
|
||||||
|
final user = firebaseAuth.currentUser;
|
||||||
|
if (user == null) {
|
||||||
|
throw Exception('User not logged in');
|
||||||
|
}
|
||||||
|
|
||||||
|
final result =
|
||||||
|
await dataConnect.getStaffByUserId(userId: user.uid).execute();
|
||||||
|
final staffs = result.data.staffs;
|
||||||
|
if (staffs.isEmpty) {
|
||||||
|
// This might happen if the user is logged in but staff profile isn't created yet or not synced.
|
||||||
|
// For now, fail hard. Code can be improved to handle this case properly.
|
||||||
|
throw Exception('No staff profile found for user');
|
||||||
|
}
|
||||||
|
|
||||||
|
_staffId = staffs.first.id;
|
||||||
|
return _staffId!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<TaxFormEntity>> getTaxForms() async {
|
||||||
|
final staffId = await _getStaffId();
|
||||||
|
final result =
|
||||||
|
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
|
||||||
|
|
||||||
|
final forms = result.data.taxForms.map((e) => _mapToEntity(e)).toList();
|
||||||
|
|
||||||
|
// Check if required forms exist, create if not.
|
||||||
|
final typesPresent = forms.map((f) => f.type).toSet();
|
||||||
|
bool createdNew = false;
|
||||||
|
|
||||||
|
if (!typesPresent.contains(TaxFormType.i9)) {
|
||||||
|
await _createInitialForm(staffId, TaxFormType.i9);
|
||||||
|
createdNew = true;
|
||||||
|
}
|
||||||
|
if (!typesPresent.contains(TaxFormType.w4)) {
|
||||||
|
await _createInitialForm(staffId, TaxFormType.w4);
|
||||||
|
createdNew = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createdNew) {
|
||||||
|
final result2 =
|
||||||
|
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
|
||||||
|
return result2.data.taxForms.map((e) => _mapToEntity(e)).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return forms;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _createInitialForm(String staffId, TaxFormType type) async {
|
||||||
|
String title = '';
|
||||||
|
String subtitle = '';
|
||||||
|
String description = '';
|
||||||
|
|
||||||
|
if (type == TaxFormType.i9) {
|
||||||
|
title = 'Form I-9';
|
||||||
|
subtitle = 'Employment Eligibility Verification';
|
||||||
|
description = 'Required for all new hires to verify identity.';
|
||||||
|
} else {
|
||||||
|
title = 'Form W-4';
|
||||||
|
subtitle = 'Employee\'s Withholding Certificate';
|
||||||
|
description = 'Determines federal income tax withholding.';
|
||||||
|
}
|
||||||
|
|
||||||
|
await dataConnect
|
||||||
|
.createTaxForm(
|
||||||
|
staffId: staffId,
|
||||||
|
formType: _mapTypeToGenerated(type),
|
||||||
|
title: title,
|
||||||
|
)
|
||||||
|
.subtitle(subtitle)
|
||||||
|
.description(description)
|
||||||
|
.status(dc.TaxFormStatus.NOT_STARTED)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> submitForm(TaxFormType type, Map<String, dynamic> data) async {
|
||||||
|
final staffId = await _getStaffId();
|
||||||
|
final result =
|
||||||
|
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
|
||||||
|
final targetTypeString = _mapTypeToGenerated(type).name;
|
||||||
|
|
||||||
|
final form = result.data.taxForms.firstWhere(
|
||||||
|
(e) => e.formType.stringValue == targetTypeString,
|
||||||
|
orElse: () => throw Exception('Form not found for submission'),
|
||||||
|
);
|
||||||
|
|
||||||
|
// AnyValue expects a scalar, list, or map.
|
||||||
|
await dataConnect
|
||||||
|
.updateTaxForm(
|
||||||
|
id: form.id,
|
||||||
|
)
|
||||||
|
.formData(AnyValue.fromJson(data))
|
||||||
|
.status(dc.TaxFormStatus.SUBMITTED)
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateFormStatus(TaxFormType type, TaxFormStatus status) async {
|
||||||
|
final staffId = await _getStaffId();
|
||||||
|
final result =
|
||||||
|
await dataConnect.getTaxFormsBystaffId(staffId: staffId).execute();
|
||||||
|
final targetTypeString = _mapTypeToGenerated(type).name;
|
||||||
|
|
||||||
|
final form = result.data.taxForms.firstWhere(
|
||||||
|
(e) => e.formType.stringValue == targetTypeString,
|
||||||
|
orElse: () => throw Exception('Form not found for update'),
|
||||||
|
);
|
||||||
|
|
||||||
|
await dataConnect
|
||||||
|
.updateTaxForm(
|
||||||
|
id: form.id,
|
||||||
|
)
|
||||||
|
.status(_mapStatusToGenerated(status))
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
TaxFormEntity _mapToEntity(dc.GetTaxFormsBystaffIdTaxForms form) {
|
||||||
|
return TaxFormEntity(
|
||||||
|
type: _mapTypeFromString(form.formType.stringValue),
|
||||||
|
title: form.title,
|
||||||
|
subtitle: form.subtitle ?? '',
|
||||||
|
description: form.description ?? '',
|
||||||
|
status: _mapStatusFromString(form.status.stringValue),
|
||||||
|
lastUpdated: form.updatedAt?.toDateTime(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
TaxFormType _mapTypeFromString(String value) {
|
||||||
|
switch (value) {
|
||||||
|
case 'I9':
|
||||||
|
return TaxFormType.i9;
|
||||||
|
case 'W4':
|
||||||
|
return TaxFormType.w4;
|
||||||
|
default:
|
||||||
|
// Handle unexpected types gracefully if needed, or throw.
|
||||||
|
// Assuming database integrity for now.
|
||||||
|
if (value == 'I-9') return TaxFormType.i9; // Fallback just in case
|
||||||
|
throw Exception('Unknown TaxFormType string: $value');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TaxFormStatus _mapStatusFromString(String value) {
|
||||||
|
switch (value) {
|
||||||
|
case 'NOT_STARTED':
|
||||||
|
return TaxFormStatus.notStarted;
|
||||||
|
case 'DRAFT':
|
||||||
|
return TaxFormStatus.inProgress;
|
||||||
|
case 'SUBMITTED':
|
||||||
|
return TaxFormStatus.submitted;
|
||||||
|
case 'APPROVED':
|
||||||
|
return TaxFormStatus.approved;
|
||||||
|
case 'REJECTED':
|
||||||
|
return TaxFormStatus.rejected;
|
||||||
|
default:
|
||||||
|
return TaxFormStatus.notStarted; // Default fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dc.TaxFormType _mapTypeToGenerated(TaxFormType type) {
|
||||||
|
switch (type) {
|
||||||
|
case TaxFormType.i9:
|
||||||
|
return dc.TaxFormType.I9;
|
||||||
|
case TaxFormType.w4:
|
||||||
|
return dc.TaxFormType.W4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dc.TaxFormStatus _mapStatusToGenerated(TaxFormStatus status) {
|
||||||
|
switch (status) {
|
||||||
|
case TaxFormStatus.notStarted:
|
||||||
|
return dc.TaxFormStatus.NOT_STARTED;
|
||||||
|
case TaxFormStatus.inProgress:
|
||||||
|
return dc.TaxFormStatus.DRAFT;
|
||||||
|
case TaxFormStatus.submitted:
|
||||||
|
return dc.TaxFormStatus.SUBMITTED;
|
||||||
|
case TaxFormStatus.approved:
|
||||||
|
return dc.TaxFormStatus.APPROVED;
|
||||||
|
case TaxFormStatus.rejected:
|
||||||
|
return dc.TaxFormStatus.REJECTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import '../entities/tax_form_entity.dart';
|
||||||
|
import '../repositories/tax_forms_repository.dart';
|
||||||
|
|
||||||
|
class GetTaxFormsUseCase {
|
||||||
|
final TaxFormsRepository _repository;
|
||||||
|
|
||||||
|
GetTaxFormsUseCase(this._repository);
|
||||||
|
|
||||||
|
Future<List<TaxFormEntity>> call() async {
|
||||||
|
return _repository.getTaxForms();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import '../entities/tax_form_entity.dart';
|
||||||
|
import '../repositories/tax_forms_repository.dart';
|
||||||
|
|
||||||
|
class SubmitTaxFormUseCase {
|
||||||
|
final TaxFormsRepository _repository;
|
||||||
|
|
||||||
|
SubmitTaxFormUseCase(this._repository);
|
||||||
|
|
||||||
|
Future<void> call(TaxFormType type, Map<String, dynamic> data) async {
|
||||||
|
return _repository.submitForm(type, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../../../domain/entities/tax_form_entity.dart';
|
import '../../../domain/entities/tax_form_entity.dart';
|
||||||
import '../../../domain/repositories/tax_forms_repository.dart';
|
import '../../../domain/usecases/submit_tax_form_usecase.dart';
|
||||||
import 'form_i9_state.dart';
|
import 'form_i9_state.dart';
|
||||||
|
|
||||||
class FormI9Cubit extends Cubit<FormI9State> {
|
class FormI9Cubit extends Cubit<FormI9State> {
|
||||||
final TaxFormsRepository _repository;
|
final SubmitTaxFormUseCase _submitTaxFormUseCase;
|
||||||
|
|
||||||
FormI9Cubit(this._repository) : super(const FormI9State());
|
FormI9Cubit(this._submitTaxFormUseCase) : super(const FormI9State());
|
||||||
|
|
||||||
void nextStep(int totalSteps) {
|
void nextStep(int totalSteps) {
|
||||||
if (state.currentStep < totalSteps - 1) {
|
if (state.currentStep < totalSteps - 1) {
|
||||||
@@ -20,45 +20,47 @@ class FormI9Cubit extends Cubit<FormI9State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateCitizenship({required bool isUsCitizen}) {
|
// Personal Info
|
||||||
emit(state.copyWith(
|
void firstNameChanged(String value) => emit(state.copyWith(firstName: value));
|
||||||
isUsCitizen: isUsCitizen,
|
void lastNameChanged(String value) => emit(state.copyWith(lastName: value));
|
||||||
isLawfulResident: false,
|
void middleInitialChanged(String value) => emit(state.copyWith(middleInitial: value));
|
||||||
));
|
void otherLastNamesChanged(String value) => emit(state.copyWith(otherLastNames: value));
|
||||||
}
|
void dobChanged(String value) => emit(state.copyWith(dob: value));
|
||||||
|
void ssnChanged(String value) => emit(state.copyWith(ssn: value));
|
||||||
|
void emailChanged(String value) => emit(state.copyWith(email: value));
|
||||||
|
void phoneChanged(String value) => emit(state.copyWith(phone: value));
|
||||||
|
|
||||||
void updateLawfulResident({required bool isLawfulResident}) {
|
// Address
|
||||||
emit(state.copyWith(
|
void addressChanged(String value) => emit(state.copyWith(address: value));
|
||||||
isLawfulResident: isLawfulResident,
|
void aptNumberChanged(String value) => emit(state.copyWith(aptNumber: value));
|
||||||
isUsCitizen: false,
|
void cityChanged(String value) => emit(state.copyWith(city: value));
|
||||||
));
|
void stateChanged(String value) => emit(state.copyWith(state: value));
|
||||||
}
|
void zipCodeChanged(String value) => emit(state.copyWith(zipCode: value));
|
||||||
|
|
||||||
void updateNonCitizen() {
|
// Citizenship
|
||||||
emit(state.copyWith(
|
void citizenshipStatusChanged(String value) => emit(state.copyWith(citizenshipStatus: value));
|
||||||
isUsCitizen: false,
|
void uscisNumberChanged(String value) => emit(state.copyWith(uscisNumber: value));
|
||||||
isLawfulResident: false,
|
void admissionNumberChanged(String value) => emit(state.copyWith(admissionNumber: value));
|
||||||
));
|
void passportNumberChanged(String value) => emit(state.copyWith(passportNumber: value));
|
||||||
}
|
void countryIssuanceChanged(String value) => emit(state.copyWith(countryIssuance: value));
|
||||||
|
|
||||||
void ssnChanged(String value) {
|
// Signature
|
||||||
emit(state.copyWith(ssn: value));
|
void preparerUsedChanged(bool value) => emit(state.copyWith(preparerUsed: value));
|
||||||
}
|
void signatureChanged(String value) => emit(state.copyWith(signature: value));
|
||||||
|
|
||||||
void alienNumberChanged(String value) {
|
|
||||||
emit(state.copyWith(alienNumber: value));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> submit() async {
|
Future<void> submit() async {
|
||||||
emit(state.copyWith(status: FormI9Status.submitting));
|
emit(state.copyWith(status: FormI9Status.submitting));
|
||||||
try {
|
try {
|
||||||
await _repository.submitForm(
|
await _submitTaxFormUseCase(
|
||||||
TaxFormType.i9,
|
TaxFormType.i9,
|
||||||
{
|
<String, dynamic>{
|
||||||
'isUsCitizen': state.isUsCitizen,
|
'firstName': state.firstName,
|
||||||
'isLawfulResident': state.isLawfulResident,
|
'lastName': state.lastName,
|
||||||
|
'middleInitial': state.middleInitial,
|
||||||
|
'citizenshipStatus': state.citizenshipStatus,
|
||||||
'ssn': state.ssn,
|
'ssn': state.ssn,
|
||||||
'alienNumber': state.alienNumber,
|
'signature': state.signature,
|
||||||
|
// ... add other fields as needed for backend
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
emit(state.copyWith(status: FormI9Status.success));
|
emit(state.copyWith(status: FormI9Status.success));
|
||||||
|
|||||||
@@ -4,38 +4,110 @@ enum FormI9Status { initial, submitting, success, failure }
|
|||||||
|
|
||||||
class FormI9State extends Equatable {
|
class FormI9State extends Equatable {
|
||||||
final int currentStep;
|
final int currentStep;
|
||||||
final bool isUsCitizen;
|
// Personal Info
|
||||||
final bool isLawfulResident;
|
final String firstName;
|
||||||
|
final String lastName;
|
||||||
|
final String middleInitial;
|
||||||
|
final String otherLastNames;
|
||||||
|
final String dob;
|
||||||
final String ssn;
|
final String ssn;
|
||||||
final String alienNumber;
|
final String email;
|
||||||
|
final String phone;
|
||||||
|
|
||||||
|
// Address
|
||||||
|
final String address;
|
||||||
|
final String aptNumber;
|
||||||
|
final String city;
|
||||||
|
final String state;
|
||||||
|
final String zipCode;
|
||||||
|
|
||||||
|
// Citizenship
|
||||||
|
final String citizenshipStatus; // citizen, noncitizen_national, permanent_resident, alien_authorized
|
||||||
|
final String uscisNumber;
|
||||||
|
final String admissionNumber;
|
||||||
|
final String passportNumber;
|
||||||
|
final String countryIssuance;
|
||||||
|
|
||||||
|
// Signature
|
||||||
|
final bool preparerUsed;
|
||||||
|
final String signature;
|
||||||
|
|
||||||
final FormI9Status status;
|
final FormI9Status status;
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
|
|
||||||
const FormI9State({
|
const FormI9State({
|
||||||
this.currentStep = 0,
|
this.currentStep = 0,
|
||||||
this.isUsCitizen = false,
|
this.firstName = '',
|
||||||
this.isLawfulResident = false,
|
this.lastName = '',
|
||||||
|
this.middleInitial = '',
|
||||||
|
this.otherLastNames = '',
|
||||||
|
this.dob = '',
|
||||||
this.ssn = '',
|
this.ssn = '',
|
||||||
this.alienNumber = '',
|
this.email = '',
|
||||||
|
this.phone = '',
|
||||||
|
this.address = '',
|
||||||
|
this.aptNumber = '',
|
||||||
|
this.city = '',
|
||||||
|
this.state = '',
|
||||||
|
this.zipCode = '',
|
||||||
|
this.citizenshipStatus = '',
|
||||||
|
this.uscisNumber = '',
|
||||||
|
this.admissionNumber = '',
|
||||||
|
this.passportNumber = '',
|
||||||
|
this.countryIssuance = '',
|
||||||
|
this.preparerUsed = false,
|
||||||
|
this.signature = '',
|
||||||
this.status = FormI9Status.initial,
|
this.status = FormI9Status.initial,
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
FormI9State copyWith({
|
FormI9State copyWith({
|
||||||
int? currentStep,
|
int? currentStep,
|
||||||
bool? isUsCitizen,
|
String? firstName,
|
||||||
bool? isLawfulResident,
|
String? lastName,
|
||||||
|
String? middleInitial,
|
||||||
|
String? otherLastNames,
|
||||||
|
String? dob,
|
||||||
String? ssn,
|
String? ssn,
|
||||||
String? alienNumber,
|
String? email,
|
||||||
|
String? phone,
|
||||||
|
String? address,
|
||||||
|
String? aptNumber,
|
||||||
|
String? city,
|
||||||
|
String? state,
|
||||||
|
String? zipCode,
|
||||||
|
String? citizenshipStatus,
|
||||||
|
String? uscisNumber,
|
||||||
|
String? admissionNumber,
|
||||||
|
String? passportNumber,
|
||||||
|
String? countryIssuance,
|
||||||
|
bool? preparerUsed,
|
||||||
|
String? signature,
|
||||||
FormI9Status? status,
|
FormI9Status? status,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
}) {
|
}) {
|
||||||
return FormI9State(
|
return FormI9State(
|
||||||
currentStep: currentStep ?? this.currentStep,
|
currentStep: currentStep ?? this.currentStep,
|
||||||
isUsCitizen: isUsCitizen ?? this.isUsCitizen,
|
firstName: firstName ?? this.firstName,
|
||||||
isLawfulResident: isLawfulResident ?? this.isLawfulResident,
|
lastName: lastName ?? this.lastName,
|
||||||
|
middleInitial: middleInitial ?? this.middleInitial,
|
||||||
|
otherLastNames: otherLastNames ?? this.otherLastNames,
|
||||||
|
dob: dob ?? this.dob,
|
||||||
ssn: ssn ?? this.ssn,
|
ssn: ssn ?? this.ssn,
|
||||||
alienNumber: alienNumber ?? this.alienNumber,
|
email: email ?? this.email,
|
||||||
|
phone: phone ?? this.phone,
|
||||||
|
address: address ?? this.address,
|
||||||
|
aptNumber: aptNumber ?? this.aptNumber,
|
||||||
|
city: city ?? this.city,
|
||||||
|
state: state ?? this.state,
|
||||||
|
zipCode: zipCode ?? this.zipCode,
|
||||||
|
citizenshipStatus: citizenshipStatus ?? this.citizenshipStatus,
|
||||||
|
uscisNumber: uscisNumber ?? this.uscisNumber,
|
||||||
|
admissionNumber: admissionNumber ?? this.admissionNumber,
|
||||||
|
passportNumber: passportNumber ?? this.passportNumber,
|
||||||
|
countryIssuance: countryIssuance ?? this.countryIssuance,
|
||||||
|
preparerUsed: preparerUsed ?? this.preparerUsed,
|
||||||
|
signature: signature ?? this.signature,
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
errorMessage: errorMessage ?? this.errorMessage,
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
);
|
);
|
||||||
@@ -44,10 +116,26 @@ class FormI9State extends Equatable {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
currentStep,
|
currentStep,
|
||||||
isUsCitizen,
|
firstName,
|
||||||
isLawfulResident,
|
lastName,
|
||||||
|
middleInitial,
|
||||||
|
otherLastNames,
|
||||||
|
dob,
|
||||||
ssn,
|
ssn,
|
||||||
alienNumber,
|
email,
|
||||||
|
phone,
|
||||||
|
address,
|
||||||
|
aptNumber,
|
||||||
|
city,
|
||||||
|
state,
|
||||||
|
zipCode,
|
||||||
|
citizenshipStatus,
|
||||||
|
uscisNumber,
|
||||||
|
admissionNumber,
|
||||||
|
passportNumber,
|
||||||
|
countryIssuance,
|
||||||
|
preparerUsed,
|
||||||
|
signature,
|
||||||
status,
|
status,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../../../domain/entities/tax_form_entity.dart';
|
import '../../../domain/entities/tax_form_entity.dart';
|
||||||
import '../../../domain/repositories/tax_forms_repository.dart';
|
import '../../../domain/usecases/get_tax_forms_usecase.dart';
|
||||||
import 'tax_forms_state.dart';
|
import 'tax_forms_state.dart';
|
||||||
|
|
||||||
class TaxFormsCubit extends Cubit<TaxFormsState> {
|
class TaxFormsCubit extends Cubit<TaxFormsState> {
|
||||||
final TaxFormsRepository _repository;
|
final GetTaxFormsUseCase _getTaxFormsUseCase;
|
||||||
|
|
||||||
TaxFormsCubit(this._repository) : super(const TaxFormsState());
|
TaxFormsCubit(this._getTaxFormsUseCase) : super(const TaxFormsState());
|
||||||
|
|
||||||
Future<void> loadTaxForms() async {
|
Future<void> loadTaxForms() async {
|
||||||
emit(state.copyWith(status: TaxFormsStatus.loading));
|
emit(state.copyWith(status: TaxFormsStatus.loading));
|
||||||
try {
|
try {
|
||||||
final List<TaxFormEntity> forms = await _repository.getTaxForms();
|
final List<TaxFormEntity> forms = await _getTaxFormsUseCase();
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
status: TaxFormsStatus.success,
|
status: TaxFormsStatus.success,
|
||||||
forms: forms,
|
forms: forms,
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import '../../../domain/entities/tax_form_entity.dart';
|
import '../../../domain/entities/tax_form_entity.dart';
|
||||||
import '../../../domain/repositories/tax_forms_repository.dart';
|
import '../../../domain/usecases/submit_tax_form_usecase.dart';
|
||||||
import 'form_w4_state.dart';
|
import 'form_w4_state.dart';
|
||||||
|
|
||||||
class FormW4Cubit extends Cubit<FormW4State> {
|
class FormW4Cubit extends Cubit<FormW4State> {
|
||||||
final TaxFormsRepository _repository;
|
final SubmitTaxFormUseCase _submitTaxFormUseCase;
|
||||||
|
|
||||||
FormW4Cubit(this._repository) : super(const FormW4State());
|
FormW4Cubit(this._submitTaxFormUseCase) : super(const FormW4State());
|
||||||
|
|
||||||
void nextStep(int totalSteps) {
|
void nextStep(int totalSteps) {
|
||||||
if (state.currentStep < totalSteps - 1) {
|
if (state.currentStep < totalSteps - 1) {
|
||||||
emit(state.copyWith(currentStep: state.currentStep + 1));
|
emit(state.copyWith(currentStep: state.currentStep + 1));
|
||||||
|
} else {
|
||||||
|
submit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,42 +22,38 @@ class FormW4Cubit extends Cubit<FormW4State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void filingStatusChanged(String status) {
|
// Personal Info
|
||||||
emit(state.copyWith(filingStatus: status));
|
void firstNameChanged(String value) => emit(state.copyWith(firstName: value));
|
||||||
}
|
void lastNameChanged(String value) => emit(state.copyWith(lastName: value));
|
||||||
|
void ssnChanged(String value) => emit(state.copyWith(ssn: value));
|
||||||
|
void addressChanged(String value) => emit(state.copyWith(address: value));
|
||||||
|
void cityStateZipChanged(String value) => emit(state.copyWith(cityStateZip: value));
|
||||||
|
|
||||||
void multipleJobsChanged(bool value) {
|
// Form Data
|
||||||
emit(state.copyWith(multipleJobs: value));
|
void filingStatusChanged(String value) => emit(state.copyWith(filingStatus: value));
|
||||||
}
|
void multipleJobsChanged(bool value) => emit(state.copyWith(multipleJobs: value));
|
||||||
|
void qualifyingChildrenChanged(int value) => emit(state.copyWith(qualifyingChildren: value));
|
||||||
void dependentsAmountChanged(String value) {
|
void otherDependentsChanged(int value) => emit(state.copyWith(otherDependents: value));
|
||||||
emit(state.copyWith(dependentsAmount: value));
|
|
||||||
}
|
// Adjustments
|
||||||
|
void otherIncomeChanged(String value) => emit(state.copyWith(otherIncome: value));
|
||||||
void otherIncomeChanged(String value) {
|
void deductionsChanged(String value) => emit(state.copyWith(deductions: value));
|
||||||
emit(state.copyWith(otherIncome: value));
|
void extraWithholdingChanged(String value) => emit(state.copyWith(extraWithholding: value));
|
||||||
}
|
void signatureChanged(String value) => emit(state.copyWith(signature: value));
|
||||||
|
|
||||||
void deductionsChanged(String value) {
|
|
||||||
emit(state.copyWith(deductions: value));
|
|
||||||
}
|
|
||||||
|
|
||||||
void extraWithholdingChanged(String value) {
|
|
||||||
emit(state.copyWith(extraWithholding: value));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> submit() async {
|
Future<void> submit() async {
|
||||||
emit(state.copyWith(status: FormW4Status.submitting));
|
emit(state.copyWith(status: FormW4Status.submitting));
|
||||||
try {
|
try {
|
||||||
await _repository.submitForm(
|
await _submitTaxFormUseCase(
|
||||||
TaxFormType.w4,
|
TaxFormType.w4,
|
||||||
{
|
<String, dynamic>{
|
||||||
|
'firstName': state.firstName,
|
||||||
|
'lastName': state.lastName,
|
||||||
|
'ssn': state.ssn,
|
||||||
'filingStatus': state.filingStatus,
|
'filingStatus': state.filingStatus,
|
||||||
'multipleJobs': state.multipleJobs,
|
'multipleJobs': state.multipleJobs,
|
||||||
'dependentsAmount': state.dependentsAmount,
|
'signature': state.signature,
|
||||||
'otherIncome': state.otherIncome,
|
// ... add other fields as needed
|
||||||
'deductions': state.deductions,
|
|
||||||
'extraWithholding': state.extraWithholding,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
emit(state.copyWith(status: FormW4Status.success));
|
emit(state.copyWith(status: FormW4Status.success));
|
||||||
|
|||||||
@@ -4,60 +4,106 @@ enum FormW4Status { initial, submitting, success, failure }
|
|||||||
|
|
||||||
class FormW4State extends Equatable {
|
class FormW4State extends Equatable {
|
||||||
final int currentStep;
|
final int currentStep;
|
||||||
|
|
||||||
|
// Personal Info
|
||||||
|
final String firstName;
|
||||||
|
final String lastName;
|
||||||
|
final String ssn;
|
||||||
|
final String address;
|
||||||
|
final String cityStateZip;
|
||||||
|
|
||||||
|
// Form Data
|
||||||
final String filingStatus;
|
final String filingStatus;
|
||||||
final bool multipleJobs;
|
final bool multipleJobs;
|
||||||
final String dependentsAmount;
|
|
||||||
|
// Dependents
|
||||||
|
final int qualifyingChildren;
|
||||||
|
final int otherDependents;
|
||||||
|
|
||||||
|
// Adjustments
|
||||||
final String otherIncome;
|
final String otherIncome;
|
||||||
final String deductions;
|
final String deductions;
|
||||||
final String extraWithholding;
|
final String extraWithholding;
|
||||||
|
|
||||||
|
final String signature;
|
||||||
final FormW4Status status;
|
final FormW4Status status;
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
|
|
||||||
const FormW4State({
|
const FormW4State({
|
||||||
this.currentStep = 0,
|
this.currentStep = 0,
|
||||||
this.filingStatus = 'Single',
|
this.firstName = '',
|
||||||
|
this.lastName = '',
|
||||||
|
this.ssn = '',
|
||||||
|
this.address = '',
|
||||||
|
this.cityStateZip = '',
|
||||||
|
this.filingStatus = '',
|
||||||
this.multipleJobs = false,
|
this.multipleJobs = false,
|
||||||
this.dependentsAmount = '',
|
this.qualifyingChildren = 0,
|
||||||
|
this.otherDependents = 0,
|
||||||
this.otherIncome = '',
|
this.otherIncome = '',
|
||||||
this.deductions = '',
|
this.deductions = '',
|
||||||
this.extraWithholding = '',
|
this.extraWithholding = '',
|
||||||
|
this.signature = '',
|
||||||
this.status = FormW4Status.initial,
|
this.status = FormW4Status.initial,
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
FormW4State copyWith({
|
FormW4State copyWith({
|
||||||
int? currentStep,
|
int? currentStep,
|
||||||
|
String? firstName,
|
||||||
|
String? lastName,
|
||||||
|
String? ssn,
|
||||||
|
String? address,
|
||||||
|
String? cityStateZip,
|
||||||
String? filingStatus,
|
String? filingStatus,
|
||||||
bool? multipleJobs,
|
bool? multipleJobs,
|
||||||
String? dependentsAmount,
|
int? qualifyingChildren,
|
||||||
|
int? otherDependents,
|
||||||
String? otherIncome,
|
String? otherIncome,
|
||||||
String? deductions,
|
String? deductions,
|
||||||
String? extraWithholding,
|
String? extraWithholding,
|
||||||
|
String? signature,
|
||||||
FormW4Status? status,
|
FormW4Status? status,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
}) {
|
}) {
|
||||||
return FormW4State(
|
return FormW4State(
|
||||||
currentStep: currentStep ?? this.currentStep,
|
currentStep: currentStep ?? this.currentStep,
|
||||||
|
firstName: firstName ?? this.firstName,
|
||||||
|
lastName: lastName ?? this.lastName,
|
||||||
|
ssn: ssn ?? this.ssn,
|
||||||
|
address: address ?? this.address,
|
||||||
|
cityStateZip: cityStateZip ?? this.cityStateZip,
|
||||||
filingStatus: filingStatus ?? this.filingStatus,
|
filingStatus: filingStatus ?? this.filingStatus,
|
||||||
multipleJobs: multipleJobs ?? this.multipleJobs,
|
multipleJobs: multipleJobs ?? this.multipleJobs,
|
||||||
dependentsAmount: dependentsAmount ?? this.dependentsAmount,
|
qualifyingChildren: qualifyingChildren ?? this.qualifyingChildren,
|
||||||
|
otherDependents: otherDependents ?? this.otherDependents,
|
||||||
otherIncome: otherIncome ?? this.otherIncome,
|
otherIncome: otherIncome ?? this.otherIncome,
|
||||||
deductions: deductions ?? this.deductions,
|
deductions: deductions ?? this.deductions,
|
||||||
extraWithholding: extraWithholding ?? this.extraWithholding,
|
extraWithholding: extraWithholding ?? this.extraWithholding,
|
||||||
|
signature: signature ?? this.signature,
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
errorMessage: errorMessage ?? this.errorMessage,
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int get totalCredits => (qualifyingChildren * 2000) + (otherDependents * 500);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
currentStep,
|
currentStep,
|
||||||
|
firstName,
|
||||||
|
lastName,
|
||||||
|
ssn,
|
||||||
|
address,
|
||||||
|
cityStateZip,
|
||||||
filingStatus,
|
filingStatus,
|
||||||
multipleJobs,
|
multipleJobs,
|
||||||
dependentsAmount,
|
qualifyingChildren,
|
||||||
|
otherDependents,
|
||||||
otherIncome,
|
otherIncome,
|
||||||
deductions,
|
deductions,
|
||||||
extraWithholding,
|
extraWithholding,
|
||||||
|
signature,
|
||||||
status,
|
status,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart' hide ModularWatchExtension;
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../blocs/i9/form_i9_cubit.dart';
|
||||||
|
import '../blocs/i9/form_i9_state.dart';
|
||||||
|
|
||||||
class FormI9Page extends StatefulWidget {
|
class FormI9Page extends StatefulWidget {
|
||||||
const FormI9Page({super.key});
|
const FormI9Page({super.key});
|
||||||
@@ -10,34 +14,6 @@ class FormI9Page extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FormI9PageState extends State<FormI9Page> {
|
class _FormI9PageState extends State<FormI9Page> {
|
||||||
int _currentStep = 0;
|
|
||||||
bool _isSubmitting = false;
|
|
||||||
bool _isSuccess = false;
|
|
||||||
|
|
||||||
final Map<String, dynamic> _formData = <String, dynamic>{
|
|
||||||
'firstName': '',
|
|
||||||
'lastName': '',
|
|
||||||
'middleInitial': '',
|
|
||||||
'otherLastNames': '',
|
|
||||||
'address': '',
|
|
||||||
'aptNumber': '',
|
|
||||||
'city': '',
|
|
||||||
'state': '',
|
|
||||||
'zipCode': '',
|
|
||||||
'dob': '',
|
|
||||||
'ssn': '',
|
|
||||||
'email': '',
|
|
||||||
'phone': '',
|
|
||||||
'citizenshipStatus': '',
|
|
||||||
'uscisNumber': '',
|
|
||||||
'admissionNumber': '',
|
|
||||||
'passportNumber': '',
|
|
||||||
'countryIssuance': '',
|
|
||||||
};
|
|
||||||
|
|
||||||
String _signature = '';
|
|
||||||
bool _preparerUsed = false;
|
|
||||||
|
|
||||||
final List<String> _usStates = <String>[
|
final List<String> _usStates = <String>[
|
||||||
'AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'FL', 'GA',
|
'AL', 'AK', 'AZ', 'AR', 'CA', 'CO', 'CT', 'DE', 'FL', 'GA',
|
||||||
'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD',
|
'HI', 'ID', 'IL', 'IN', 'IA', 'KS', 'KY', 'LA', 'ME', 'MD',
|
||||||
@@ -53,76 +29,74 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
<String, String>{'title': 'Review & Sign', 'subtitle': 'Confirm your information'},
|
<String, String>{'title': 'Review & Sign', 'subtitle': 'Confirm your information'},
|
||||||
];
|
];
|
||||||
|
|
||||||
void _updateField(String key, dynamic value) {
|
bool _canProceed(FormI9State state) {
|
||||||
setState(() {
|
switch (state.currentStep) {
|
||||||
_formData[key] = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _canProceed() {
|
|
||||||
switch (_currentStep) {
|
|
||||||
case 0:
|
case 0:
|
||||||
return (_formData['firstName'] as String).trim().isNotEmpty &&
|
return state.firstName.trim().isNotEmpty &&
|
||||||
(_formData['lastName'] as String).trim().isNotEmpty &&
|
state.lastName.trim().isNotEmpty &&
|
||||||
(_formData['dob'] as String).isNotEmpty &&
|
state.dob.isNotEmpty &&
|
||||||
(_formData['ssn'] as String).replaceAll(RegExp(r'\D'), '').length >= 9;
|
state.ssn.replaceAll(RegExp(r'\D'), '').length >= 9;
|
||||||
case 1:
|
case 1:
|
||||||
return (_formData['address'] as String).trim().isNotEmpty &&
|
return state.address.trim().isNotEmpty &&
|
||||||
(_formData['city'] as String).trim().isNotEmpty &&
|
state.city.trim().isNotEmpty &&
|
||||||
(_formData['state'] as String).isNotEmpty &&
|
state.state.isNotEmpty &&
|
||||||
(_formData['zipCode'] as String).isNotEmpty;
|
state.zipCode.isNotEmpty;
|
||||||
case 2:
|
case 2:
|
||||||
return (_formData['citizenshipStatus'] as String).isNotEmpty;
|
return state.citizenshipStatus.isNotEmpty;
|
||||||
case 3:
|
case 3:
|
||||||
return _signature.trim().isNotEmpty;
|
return state.signature.trim().isNotEmpty;
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleNext() {
|
void _handleNext(BuildContext context, int currentStep) {
|
||||||
if (_currentStep < _steps.length - 1) {
|
if (currentStep < _steps.length - 1) {
|
||||||
setState(() => _currentStep++);
|
context.read<FormI9Cubit>().nextStep(_steps.length);
|
||||||
} else {
|
} else {
|
||||||
_submitForm();
|
context.read<FormI9Cubit>().submit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleBack() {
|
void _handleBack(BuildContext context) {
|
||||||
if (_currentStep > 0) {
|
context.read<FormI9Cubit>().previousStep();
|
||||||
setState(() => _currentStep--);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _submitForm() async {
|
|
||||||
setState(() => _isSubmitting = true);
|
|
||||||
// Mock API call
|
|
||||||
await Future<void>.delayed(const Duration(seconds: 2));
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_isSubmitting = false;
|
|
||||||
_isSuccess = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_isSuccess) return _buildSuccessView();
|
return BlocProvider<FormI9Cubit>.value(
|
||||||
|
value: Modular.get<FormI9Cubit>(),
|
||||||
|
child: BlocConsumer<FormI9Cubit, FormI9State>(
|
||||||
|
listener: (BuildContext context, FormI9State state) {
|
||||||
|
if (state.status == FormI9Status.success) {
|
||||||
|
// Success view is handled by state check in build or we can navigate
|
||||||
|
} else if (state.status == FormI9Status.failure) {
|
||||||
|
final ScaffoldMessengerState messenger = ScaffoldMessenger.of(context);
|
||||||
|
messenger.hideCurrentSnackBar();
|
||||||
|
messenger.showSnackBar(
|
||||||
|
SnackBar(content: Text(state.errorMessage ?? 'An error occurred')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
builder: (BuildContext context, FormI9State state) {
|
||||||
|
if (state.status == FormI9Status.success) return _buildSuccessView();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: UiColors.background,
|
backgroundColor: UiColors.background,
|
||||||
body: Column(
|
body: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildHeader(),
|
_buildHeader(context, state),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
|
||||||
child: _buildCurrentStep(),
|
child: _buildCurrentStep(context, state),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildFooter(context, state),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
_buildFooter(),
|
},
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -194,7 +168,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHeader() {
|
Widget _buildHeader(BuildContext context, FormI9State state) {
|
||||||
return Container(
|
return Container(
|
||||||
color: UiColors.primary,
|
color: UiColors.primary,
|
||||||
padding: const EdgeInsets.only(top: 60, bottom: 24, left: 20, right: 20),
|
padding: const EdgeInsets.only(top: 60, bottom: 24, left: 20, right: 20),
|
||||||
@@ -241,7 +215,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
height: 4,
|
height: 4,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: idx <= _currentStep
|
color: idx <= state.currentStep
|
||||||
? UiColors.bgPopup
|
? UiColors.bgPopup
|
||||||
: UiColors.bgPopup.withOpacity(0.3),
|
: UiColors.bgPopup.withOpacity(0.3),
|
||||||
borderRadius: BorderRadius.circular(2),
|
borderRadius: BorderRadius.circular(2),
|
||||||
@@ -259,12 +233,12 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
'Step ${_currentStep + 1} of ${_steps.length}',
|
'Step ${state.currentStep + 1} of ${_steps.length}',
|
||||||
style: UiTypography.body3r.copyWith(color: UiColors.bgPopup.withOpacity(0.7)),
|
style: UiTypography.body3r.copyWith(color: UiColors.bgPopup.withOpacity(0.7)),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
_steps[_currentStep]['title']!,
|
_steps[state.currentStep]['title']!,
|
||||||
textAlign: TextAlign.end,
|
textAlign: TextAlign.end,
|
||||||
style: UiTypography.body3m.copyWith(
|
style: UiTypography.body3m.copyWith(
|
||||||
color: UiColors.bgPopup,
|
color: UiColors.bgPopup,
|
||||||
@@ -279,27 +253,27 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCurrentStep() {
|
Widget _buildCurrentStep(BuildContext context, FormI9State state) {
|
||||||
switch (_currentStep) {
|
switch (state.currentStep) {
|
||||||
case 0:
|
case 0:
|
||||||
return _buildStep1();
|
return _buildStep1(context, state);
|
||||||
case 1:
|
case 1:
|
||||||
return _buildStep2();
|
return _buildStep2(context, state);
|
||||||
case 2:
|
case 2:
|
||||||
return _buildStep3();
|
return _buildStep3(context, state);
|
||||||
case 3:
|
case 3:
|
||||||
return _buildStep4();
|
return _buildStep4(context, state);
|
||||||
default:
|
default:
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTextField(
|
Widget _buildTextField(
|
||||||
String label,
|
String label, {
|
||||||
String key, {
|
required String value,
|
||||||
|
required ValueChanged<String> onChanged,
|
||||||
TextInputType? keyboardType,
|
TextInputType? keyboardType,
|
||||||
String? placeholder,
|
String? placeholder,
|
||||||
Function(String)? onChanged,
|
|
||||||
}) {
|
}) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -313,11 +287,11 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
TextField(
|
TextField(
|
||||||
controller: TextEditingController(text: _formData[key].toString())
|
controller: TextEditingController(text: value)
|
||||||
..selection = TextSelection.fromPosition(
|
..selection = TextSelection.fromPosition(
|
||||||
TextPosition(offset: (_formData[key].toString()).length),
|
TextPosition(offset: value.length),
|
||||||
),
|
),
|
||||||
onChanged: onChanged ?? (String val) => _updateField(key, val),
|
onChanged: onChanged,
|
||||||
keyboardType: keyboardType,
|
keyboardType: keyboardType,
|
||||||
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary),
|
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -347,7 +321,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStep1() {
|
Widget _buildStep1(BuildContext context, FormI9State state) {
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Row(
|
Row(
|
||||||
@@ -355,7 +329,8 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _buildTextField(
|
child: _buildTextField(
|
||||||
'First Name *',
|
'First Name *',
|
||||||
'firstName',
|
value: state.firstName,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().firstNameChanged(val),
|
||||||
placeholder: 'John',
|
placeholder: 'John',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -363,7 +338,8 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _buildTextField(
|
child: _buildTextField(
|
||||||
'Last Name *',
|
'Last Name *',
|
||||||
'lastName',
|
value: state.lastName,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().lastNameChanged(val),
|
||||||
placeholder: 'Smith',
|
placeholder: 'Smith',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -375,7 +351,8 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _buildTextField(
|
child: _buildTextField(
|
||||||
'Middle Initial',
|
'Middle Initial',
|
||||||
'middleInitial',
|
value: state.middleInitial,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().middleInitialChanged(val),
|
||||||
placeholder: 'A',
|
placeholder: 'A',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -384,7 +361,8 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
flex: 2,
|
flex: 2,
|
||||||
child: _buildTextField(
|
child: _buildTextField(
|
||||||
'Other Last Names',
|
'Other Last Names',
|
||||||
'otherLastNames',
|
value: state.otherLastNames,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().otherLastNamesChanged(val),
|
||||||
placeholder: 'Maiden name (if any)',
|
placeholder: 'Maiden name (if any)',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -393,33 +371,36 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'Date of Birth *',
|
'Date of Birth *',
|
||||||
'dob',
|
value: state.dob,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().dobChanged(val),
|
||||||
placeholder: 'MM/DD/YYYY',
|
placeholder: 'MM/DD/YYYY',
|
||||||
keyboardType: TextInputType.datetime,
|
keyboardType: TextInputType.datetime,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'Social Security Number *',
|
'Social Security Number *',
|
||||||
'ssn',
|
value: state.ssn,
|
||||||
placeholder: 'XXX-XX-XXXX',
|
placeholder: 'XXX-XX-XXXX',
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
onChanged: (String val) {
|
onChanged: (String val) {
|
||||||
String text = val.replaceAll(RegExp(r'\D'), '');
|
String text = val.replaceAll(RegExp(r'\D'), '');
|
||||||
if (text.length > 9) text = text.substring(0, 9);
|
if (text.length > 9) text = text.substring(0, 9);
|
||||||
_updateField('ssn', text);
|
context.read<FormI9Cubit>().ssnChanged(text);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'Email Address',
|
'Email Address',
|
||||||
'email',
|
value: state.email,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().emailChanged(val),
|
||||||
keyboardType: TextInputType.emailAddress,
|
keyboardType: TextInputType.emailAddress,
|
||||||
placeholder: 'john.smith@example.com',
|
placeholder: 'john.smith@example.com',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'Phone Number',
|
'Phone Number',
|
||||||
'phone',
|
value: state.phone,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().phoneChanged(val),
|
||||||
keyboardType: TextInputType.phone,
|
keyboardType: TextInputType.phone,
|
||||||
placeholder: '(555) 555-5555',
|
placeholder: '(555) 555-5555',
|
||||||
),
|
),
|
||||||
@@ -427,18 +408,20 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStep2() {
|
Widget _buildStep2(BuildContext context, FormI9State state) {
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'Address (Street Number and Name) *',
|
'Address (Street Number and Name) *',
|
||||||
'address',
|
value: state.address,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().addressChanged(val),
|
||||||
placeholder: '123 Main Street',
|
placeholder: '123 Main Street',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'Apt. Number',
|
'Apt. Number',
|
||||||
'aptNumber',
|
value: state.aptNumber,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().aptNumberChanged(val),
|
||||||
placeholder: '4B',
|
placeholder: '4B',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
@@ -448,7 +431,8 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
flex: 2,
|
flex: 2,
|
||||||
child: _buildTextField(
|
child: _buildTextField(
|
||||||
'City or Town *',
|
'City or Town *',
|
||||||
'city',
|
value: state.city,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().cityChanged(val),
|
||||||
placeholder: 'San Francisco',
|
placeholder: 'San Francisco',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -466,12 +450,12 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
DropdownButtonFormField<String>(
|
DropdownButtonFormField<String>(
|
||||||
value: _formData['state'].toString().isEmpty ? null : _formData['state'].toString(),
|
value: state.state.isEmpty ? null : state.state,
|
||||||
onChanged: (String? val) => _updateField('state', val),
|
onChanged: (String? val) => context.read<FormI9Cubit>().stateChanged(val ?? ''),
|
||||||
items: _usStates.map((String state) {
|
items: _usStates.map((String stateAbbr) {
|
||||||
return DropdownMenuItem<String>(
|
return DropdownMenuItem<String>(
|
||||||
value: state,
|
value: stateAbbr,
|
||||||
child: Text(state),
|
child: Text(stateAbbr),
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -496,7 +480,8 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'ZIP Code *',
|
'ZIP Code *',
|
||||||
'zipCode',
|
value: state.zipCode,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().zipCodeChanged(val),
|
||||||
placeholder: '94103',
|
placeholder: '94103',
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
),
|
),
|
||||||
@@ -504,7 +489,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStep3() {
|
Widget _buildStep3(BuildContext context, FormI9State state) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@@ -514,24 +499,31 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
_buildRadioOption(
|
_buildRadioOption(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
'citizen',
|
'citizen',
|
||||||
'1. A citizen of the United States',
|
'1. A citizen of the United States',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildRadioOption(
|
_buildRadioOption(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
'noncitizen_national',
|
'noncitizen_national',
|
||||||
'2. A noncitizen national of the United States',
|
'2. A noncitizen national of the United States',
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildRadioOption(
|
_buildRadioOption(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
'permanent_resident',
|
'permanent_resident',
|
||||||
'3. A lawful permanent resident',
|
'3. A lawful permanent resident',
|
||||||
child: _formData['citizenshipStatus'] == 'permanent_resident'
|
child: state.citizenshipStatus == 'permanent_resident'
|
||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.only(top: 12),
|
padding: const EdgeInsets.only(top: 12),
|
||||||
child: _buildTextField(
|
child: _buildTextField(
|
||||||
'USCIS Number',
|
'USCIS Number',
|
||||||
'uscisNumber',
|
value: state.uscisNumber,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().uscisNumberChanged(val),
|
||||||
placeholder: 'A-123456789',
|
placeholder: 'A-123456789',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -539,26 +531,31 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildRadioOption(
|
_buildRadioOption(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
'alien_authorized',
|
'alien_authorized',
|
||||||
'4. An alien authorized to work',
|
'4. An alien authorized to work',
|
||||||
child: _formData['citizenshipStatus'] == 'alien_authorized'
|
child: state.citizenshipStatus == 'alien_authorized'
|
||||||
? Padding(
|
? Padding(
|
||||||
padding: const EdgeInsets.only(top: 12),
|
padding: const EdgeInsets.only(top: 12),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'USCIS/Admission Number',
|
'USCIS/Admission Number',
|
||||||
'admissionNumber',
|
value: state.admissionNumber,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().admissionNumberChanged(val),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'Foreign Passport Number',
|
'Foreign Passport Number',
|
||||||
'passportNumber',
|
value: state.passportNumber,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().passportNumberChanged(val),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'Country of Issuance',
|
'Country of Issuance',
|
||||||
'countryIssuance',
|
value: state.countryIssuance,
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().countryIssuanceChanged(val),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -569,10 +566,10 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRadioOption(String value, String label, {Widget? child}) {
|
Widget _buildRadioOption(BuildContext context, FormI9State state, String value, String label, {Widget? child}) {
|
||||||
final bool isSelected = _formData['citizenshipStatus'] == value;
|
final bool isSelected = state.citizenshipStatus == value;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => _updateField('citizenshipStatus', value),
|
onTap: () => context.read<FormI9Cubit>().citizenshipStatusChanged(value),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -616,7 +613,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStep4() {
|
Widget _buildStep4(BuildContext context, FormI9State state) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@@ -635,20 +632,18 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
style: UiTypography.headline4m.copyWith(fontSize: 14),
|
style: UiTypography.headline4m.copyWith(fontSize: 14),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildSummaryRow('Name', '${_formData['firstName']} ${_formData['lastName']}'),
|
_buildSummaryRow('Name', '${state.firstName} ${state.lastName}'),
|
||||||
_buildSummaryRow('Address', '${_formData['address']}, ${_formData['city']}'),
|
_buildSummaryRow('Address', '${state.address}, ${state.city}'),
|
||||||
_buildSummaryRow('SSN', '***-**-${(_formData['ssn'] as String).length >= 4 ? (_formData['ssn'] as String).substring((_formData['ssn'] as String).length - 4) : '****'}'),
|
_buildSummaryRow('SSN', '***-**-${state.ssn.length >= 4 ? state.ssn.substring(state.ssn.length - 4) : '****'}'),
|
||||||
_buildSummaryRow('Citizenship', _getReadableCitizenship(_formData['citizenshipStatus'] as String)),
|
_buildSummaryRow('Citizenship', _getReadableCitizenship(state.citizenshipStatus)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
CheckboxListTile(
|
CheckboxListTile(
|
||||||
value: _preparerUsed,
|
value: state.preparerUsed,
|
||||||
onChanged: (bool? val) {
|
onChanged: (bool? val) {
|
||||||
setState(() {
|
context.read<FormI9Cubit>().preparerUsedChanged(val ?? false);
|
||||||
_preparerUsed = val ?? false;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
title: Text(
|
title: Text(
|
||||||
@@ -679,7 +674,11 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
TextField(
|
TextField(
|
||||||
onChanged: (String val) => setState(() => _signature = val),
|
controller: TextEditingController(text: state.signature)
|
||||||
|
..selection = TextSelection.fromPosition(
|
||||||
|
TextPosition(offset: state.signature.length),
|
||||||
|
),
|
||||||
|
onChanged: (String val) => context.read<FormI9Cubit>().signatureChanged(val),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Type your full name',
|
hintText: 'Type your full name',
|
||||||
filled: true,
|
filled: true,
|
||||||
@@ -767,7 +766,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFooter() {
|
Widget _buildFooter(BuildContext context, FormI9State state) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
@@ -777,12 +776,12 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if (_currentStep > 0)
|
if (state.currentStep > 0)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: 12),
|
padding: const EdgeInsets.only(right: 12),
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: _handleBack,
|
onPressed: () => _handleBack(context),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
side: const BorderSide(color: UiColors.border),
|
side: const BorderSide(color: UiColors.border),
|
||||||
@@ -807,8 +806,8 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: (_canProceed() && !_isSubmitting)
|
onPressed: (_canProceed(state) && state.status != FormI9Status.submitting)
|
||||||
? _handleNext
|
? () => _handleNext(context, state.currentStep)
|
||||||
: null,
|
: null,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: UiColors.primary,
|
backgroundColor: UiColors.primary,
|
||||||
@@ -820,7 +819,7 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
),
|
),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
child: _isSubmitting
|
child: state.status == FormI9Status.submitting
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
@@ -833,11 +832,11 @@ class _FormI9PageState extends State<FormI9Page> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
_currentStep == _steps.length - 1
|
state.currentStep == _steps.length - 1
|
||||||
? 'Sign & Submit'
|
? 'Sign & Submit'
|
||||||
: 'Continue',
|
: 'Continue',
|
||||||
),
|
),
|
||||||
if (_currentStep < _steps.length - 1) ...<Widget>[
|
if (state.currentStep < _steps.length - 1) ...<Widget>[
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
const Icon(UiIcons.arrowRight, size: 16, color: UiColors.bgPopup),
|
const Icon(UiIcons.arrowRight, size: 16, color: UiColors.bgPopup),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
import 'package:design_system/design_system.dart';
|
import 'package:design_system/design_system.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart' hide ModularWatchExtension;
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../blocs/w4/form_w4_cubit.dart';
|
||||||
|
import '../blocs/w4/form_w4_state.dart';
|
||||||
|
|
||||||
class FormW4Page extends StatefulWidget {
|
class FormW4Page extends StatefulWidget {
|
||||||
const FormW4Page({super.key});
|
const FormW4Page({super.key});
|
||||||
@@ -10,27 +14,6 @@ class FormW4Page extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FormW4PageState extends State<FormW4Page> {
|
class _FormW4PageState extends State<FormW4Page> {
|
||||||
int _currentStep = 0;
|
|
||||||
bool _isSubmitting = false;
|
|
||||||
bool _isSuccess = false;
|
|
||||||
|
|
||||||
final Map<String, dynamic> _formData = <String, dynamic>{
|
|
||||||
'firstName': '',
|
|
||||||
'lastName': '',
|
|
||||||
'address': '',
|
|
||||||
'cityStateZip': '',
|
|
||||||
'ssn': '',
|
|
||||||
'filingStatus': '',
|
|
||||||
'multipleJobs': false,
|
|
||||||
'qualifyingChildren': 0,
|
|
||||||
'otherDependents': 0,
|
|
||||||
'otherIncome': '',
|
|
||||||
'deductions': '',
|
|
||||||
'extraWithholding': '',
|
|
||||||
};
|
|
||||||
|
|
||||||
String _signature = '';
|
|
||||||
|
|
||||||
final List<Map<String, String>> _steps = <Map<String, String>>[
|
final List<Map<String, String>> _steps = <Map<String, String>>[
|
||||||
<String, String>{'title': 'Personal Information', 'subtitle': 'Step 1'},
|
<String, String>{'title': 'Personal Information', 'subtitle': 'Step 1'},
|
||||||
<String, String>{'title': 'Filing Status', 'subtitle': 'Step 1c'},
|
<String, String>{'title': 'Filing Status', 'subtitle': 'Step 1c'},
|
||||||
@@ -40,76 +23,75 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
<String, String>{'title': 'Review & Sign', 'subtitle': 'Step 5'},
|
<String, String>{'title': 'Review & Sign', 'subtitle': 'Step 5'},
|
||||||
];
|
];
|
||||||
|
|
||||||
void _updateField(String key, dynamic value) {
|
bool _canProceed(FormW4State state) {
|
||||||
setState(() {
|
switch (state.currentStep) {
|
||||||
_formData[key] = value;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _canProceed() {
|
|
||||||
switch (_currentStep) {
|
|
||||||
case 0:
|
case 0:
|
||||||
return (_formData['firstName'] as String).trim().isNotEmpty &&
|
return state.firstName.trim().isNotEmpty &&
|
||||||
(_formData['lastName'] as String).trim().isNotEmpty &&
|
state.lastName.trim().isNotEmpty &&
|
||||||
(_formData['ssn'] as String).replaceAll(RegExp(r'\D'), '').length >= 4 &&
|
state.ssn.replaceAll(RegExp(r'\D'), '').length >= 4 &&
|
||||||
(_formData['address'] as String).trim().isNotEmpty;
|
state.address.trim().isNotEmpty;
|
||||||
case 1:
|
case 1:
|
||||||
return (_formData['filingStatus'] as String).isNotEmpty;
|
return state.filingStatus.isNotEmpty;
|
||||||
case 5:
|
case 5:
|
||||||
return _signature.trim().isNotEmpty;
|
return state.signature.trim().isNotEmpty;
|
||||||
default:
|
default:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleNext() {
|
void _handleNext(BuildContext context, int currentStep) {
|
||||||
if (_currentStep < _steps.length - 1) {
|
if (currentStep < _steps.length - 1) {
|
||||||
setState(() => _currentStep++);
|
context.read<FormW4Cubit>().nextStep(_steps.length);
|
||||||
} else {
|
} else {
|
||||||
_submitForm();
|
context.read<FormW4Cubit>().submit();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleBack() {
|
void _handleBack(BuildContext context) {
|
||||||
if (_currentStep > 0) {
|
context.read<FormW4Cubit>().previousStep();
|
||||||
setState(() => _currentStep--);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _submitForm() async {
|
|
||||||
setState(() => _isSubmitting = true);
|
|
||||||
// Mock API call
|
|
||||||
await Future<void>.delayed(const Duration(seconds: 2));
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
_isSubmitting = false;
|
|
||||||
_isSuccess = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int get _totalCredits {
|
int _totalCredits(FormW4State state) {
|
||||||
return ((_formData['qualifyingChildren'] as int) * 2000) +
|
return (state.qualifyingChildren * 2000) +
|
||||||
((_formData['otherDependents'] as int) * 500);
|
(state.otherDependents * 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (_isSuccess) return _buildSuccessView();
|
return BlocProvider<FormW4Cubit>.value(
|
||||||
|
value: Modular.get<FormW4Cubit>(),
|
||||||
|
child: BlocConsumer<FormW4Cubit, FormW4State>(
|
||||||
|
listener: (BuildContext context, FormW4State state) {
|
||||||
|
if (state.status == FormW4Status.success) {
|
||||||
|
// Handled in builder
|
||||||
|
} else if (state.status == FormW4Status.failure) {
|
||||||
|
final ScaffoldMessengerState messenger = ScaffoldMessenger.of(context);
|
||||||
|
messenger.hideCurrentSnackBar();
|
||||||
|
messenger.showSnackBar(
|
||||||
|
SnackBar(content: Text(state.errorMessage ?? 'An error occurred')),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
builder: (BuildContext context, FormW4State state) {
|
||||||
|
if (state.status == FormW4Status.success) return _buildSuccessView();
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: UiColors.background,
|
backgroundColor: UiColors.background,
|
||||||
body: Column(
|
body: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildHeader(),
|
_buildHeader(context, state),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
|
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24),
|
||||||
child: _buildCurrentStep(),
|
child: _buildCurrentStep(context, state),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
_buildFooter(context, state),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
_buildFooter(),
|
},
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -181,7 +163,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildHeader() {
|
Widget _buildHeader(BuildContext context, FormW4State state) {
|
||||||
return Container(
|
return Container(
|
||||||
color: UiColors.primary,
|
color: UiColors.primary,
|
||||||
padding: const EdgeInsets.only(top: 60, bottom: 24, left: 20, right: 20),
|
padding: const EdgeInsets.only(top: 60, bottom: 24, left: 20, right: 20),
|
||||||
@@ -205,7 +187,6 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
Text(
|
Text(
|
||||||
'Form W-4',
|
'Form W-4',
|
||||||
style: UiTypography.headline4m.copyWith(
|
style: UiTypography.headline4m.copyWith(
|
||||||
|
|
||||||
color: UiColors.bgPopup,
|
color: UiColors.bgPopup,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -229,7 +210,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
child: Container(
|
child: Container(
|
||||||
height: 4,
|
height: 4,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: idx <= _currentStep
|
color: idx <= state.currentStep
|
||||||
? UiColors.bgPopup
|
? UiColors.bgPopup
|
||||||
: UiColors.bgPopup.withOpacity(0.3),
|
: UiColors.bgPopup.withOpacity(0.3),
|
||||||
borderRadius: BorderRadius.circular(2),
|
borderRadius: BorderRadius.circular(2),
|
||||||
@@ -247,11 +228,11 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
'Step ${_currentStep + 1} of ${_steps.length}',
|
'Step ${state.currentStep + 1} of ${_steps.length}',
|
||||||
style: UiTypography.body3r.copyWith(color: UiColors.bgPopup.withOpacity(0.7)),
|
style: UiTypography.body3r.copyWith(color: UiColors.bgPopup.withOpacity(0.7)),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
_steps[_currentStep]['title']!,
|
_steps[state.currentStep]['title']!,
|
||||||
style: UiTypography.body3m.copyWith(
|
style: UiTypography.body3m.copyWith(
|
||||||
color: UiColors.bgPopup,
|
color: UiColors.bgPopup,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -264,31 +245,31 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCurrentStep() {
|
Widget _buildCurrentStep(BuildContext context, FormW4State state) {
|
||||||
switch (_currentStep) {
|
switch (state.currentStep) {
|
||||||
case 0:
|
case 0:
|
||||||
return _buildStep1();
|
return _buildStep1(context, state);
|
||||||
case 1:
|
case 1:
|
||||||
return _buildStep2();
|
return _buildStep2(context, state);
|
||||||
case 2:
|
case 2:
|
||||||
return _buildStep3();
|
return _buildStep3(context, state);
|
||||||
case 3:
|
case 3:
|
||||||
return _buildStep4();
|
return _buildStep4(context, state);
|
||||||
case 4:
|
case 4:
|
||||||
return _buildStep5();
|
return _buildStep5(context, state);
|
||||||
case 5:
|
case 5:
|
||||||
return _buildStep6();
|
return _buildStep6(context, state);
|
||||||
default:
|
default:
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTextField(
|
Widget _buildTextField(
|
||||||
String label,
|
String label, {
|
||||||
String key, {
|
required String value,
|
||||||
|
required ValueChanged<String> onChanged,
|
||||||
TextInputType? keyboardType,
|
TextInputType? keyboardType,
|
||||||
String? placeholder,
|
String? placeholder,
|
||||||
Function(String)? onChanged,
|
|
||||||
}) {
|
}) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -302,11 +283,11 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
TextField(
|
TextField(
|
||||||
controller: TextEditingController(text: _formData[key].toString())
|
controller: TextEditingController(text: value)
|
||||||
..selection = TextSelection.fromPosition(
|
..selection = TextSelection.fromPosition(
|
||||||
TextPosition(offset: (_formData[key].toString()).length),
|
TextPosition(offset: value.length),
|
||||||
),
|
),
|
||||||
onChanged: onChanged ?? (String val) => _updateField(key, val),
|
onChanged: onChanged,
|
||||||
keyboardType: keyboardType,
|
keyboardType: keyboardType,
|
||||||
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary),
|
style: UiTypography.body2r.copyWith(color: UiColors.textPrimary),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@@ -336,7 +317,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStep1() {
|
Widget _buildStep1(BuildContext context, FormW4State state) {
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Row(
|
Row(
|
||||||
@@ -344,7 +325,8 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _buildTextField(
|
child: _buildTextField(
|
||||||
'First Name *',
|
'First Name *',
|
||||||
'firstName',
|
value: state.firstName,
|
||||||
|
onChanged: (String val) => context.read<FormW4Cubit>().firstNameChanged(val),
|
||||||
placeholder: 'John',
|
placeholder: 'John',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -352,7 +334,8 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: _buildTextField(
|
child: _buildTextField(
|
||||||
'Last Name *',
|
'Last Name *',
|
||||||
'lastName',
|
value: state.lastName,
|
||||||
|
onChanged: (String val) => context.read<FormW4Cubit>().lastNameChanged(val),
|
||||||
placeholder: 'Smith',
|
placeholder: 'Smith',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -361,28 +344,34 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'Social Security Number *',
|
'Social Security Number *',
|
||||||
'ssn',
|
value: state.ssn,
|
||||||
placeholder: 'XXX-XX-XXXX',
|
placeholder: 'XXX-XX-XXXX',
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
onChanged: (String val) {
|
onChanged: (String val) {
|
||||||
String text = val.replaceAll(RegExp(r'\D'), '');
|
String text = val.replaceAll(RegExp(r'\D'), '');
|
||||||
if (text.length > 9) text = text.substring(0, 9);
|
if (text.length > 9) text = text.substring(0, 9);
|
||||||
_updateField('ssn', text);
|
context.read<FormW4Cubit>().ssnChanged(text);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildTextField('Address *', 'address', placeholder: '123 Main Street'),
|
_buildTextField(
|
||||||
|
'Address *',
|
||||||
|
value: state.address,
|
||||||
|
onChanged: (String val) => context.read<FormW4Cubit>().addressChanged(val),
|
||||||
|
placeholder: '123 Main Street',
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'City, State, ZIP',
|
'City, State, ZIP',
|
||||||
'cityStateZip',
|
value: state.cityStateZip,
|
||||||
|
onChanged: (String val) => context.read<FormW4Cubit>().cityStateZipChanged(val),
|
||||||
placeholder: 'San Francisco, CA 94102',
|
placeholder: 'San Francisco, CA 94102',
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStep2() {
|
Widget _buildStep2(BuildContext context, FormW4State state) {
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
@@ -406,18 +395,24 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
_buildRadioOption(
|
_buildRadioOption(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
'single',
|
'single',
|
||||||
'Single or Married filing separately',
|
'Single or Married filing separately',
|
||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildRadioOption(
|
_buildRadioOption(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
'married',
|
'married',
|
||||||
'Married filing jointly or Qualifying surviving spouse',
|
'Married filing jointly or Qualifying surviving spouse',
|
||||||
null,
|
null,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildRadioOption(
|
_buildRadioOption(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
'head_of_household',
|
'head_of_household',
|
||||||
'Head of household',
|
'Head of household',
|
||||||
'Check only if you\'re unmarried and pay more than half the costs of keeping up a home',
|
'Check only if you\'re unmarried and pay more than half the costs of keeping up a home',
|
||||||
@@ -426,10 +421,10 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRadioOption(String value, String label, String? subLabel) {
|
Widget _buildRadioOption(BuildContext context, FormW4State state, String value, String label, String? subLabel) {
|
||||||
final bool isSelected = _formData['filingStatus'] == value;
|
final bool isSelected = state.filingStatus == value;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => _updateField('filingStatus', value),
|
onTap: () => context.read<FormW4Cubit>().filingStatusChanged(value),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -484,7 +479,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStep3() {
|
Widget _buildStep3(BuildContext context, FormW4State state) {
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
@@ -527,14 +522,14 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () => _updateField('multipleJobs', !_formData['multipleJobs']),
|
onTap: () => context.read<FormW4Cubit>().multipleJobsChanged(!state.multipleJobs),
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: UiColors.bgPopup,
|
color: UiColors.bgPopup,
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: (_formData['multipleJobs'] as bool)
|
color: state.multipleJobs
|
||||||
? UiColors.primary
|
? UiColors.primary
|
||||||
: UiColors.border,
|
: UiColors.border,
|
||||||
),
|
),
|
||||||
@@ -546,17 +541,17 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
width: 24,
|
width: 24,
|
||||||
height: 24,
|
height: 24,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: (_formData['multipleJobs'] as bool)
|
color: state.multipleJobs
|
||||||
? UiColors.primary
|
? UiColors.primary
|
||||||
: UiColors.bgPopup,
|
: UiColors.bgPopup,
|
||||||
borderRadius: BorderRadius.circular(6),
|
borderRadius: BorderRadius.circular(6),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: (_formData['multipleJobs'] as bool)
|
color: state.multipleJobs
|
||||||
? UiColors.primary
|
? UiColors.primary
|
||||||
: Colors.grey,
|
: Colors.grey,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: (_formData['multipleJobs'] as bool)
|
child: state.multipleJobs
|
||||||
? const Icon(
|
? const Icon(
|
||||||
UiIcons.check,
|
UiIcons.check,
|
||||||
color: UiColors.bgPopup,
|
color: UiColors.bgPopup,
|
||||||
@@ -599,7 +594,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStep4() {
|
Widget _buildStep4(BuildContext context, FormW4State state) {
|
||||||
return Column(
|
return Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
@@ -632,23 +627,29 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildCounter(
|
_buildCounter(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
'Qualifying children under age 17',
|
'Qualifying children under age 17',
|
||||||
'\$2,000 each',
|
'\$2,000 each',
|
||||||
'qualifyingChildren',
|
(FormW4State s) => s.qualifyingChildren,
|
||||||
|
(int val) => context.read<FormW4Cubit>().qualifyingChildrenChanged(val),
|
||||||
),
|
),
|
||||||
const Padding(
|
const Padding(
|
||||||
padding: EdgeInsets.symmetric(vertical: 16),
|
padding: EdgeInsets.symmetric(vertical: 16),
|
||||||
child: Divider(height: 1, color: UiColors.border),
|
child: Divider(height: 1, color: UiColors.border),
|
||||||
),
|
),
|
||||||
_buildCounter(
|
_buildCounter(
|
||||||
|
context,
|
||||||
|
state,
|
||||||
'Other dependents',
|
'Other dependents',
|
||||||
'\$500 each',
|
'\$500 each',
|
||||||
'otherDependents',
|
(FormW4State s) => s.otherDependents,
|
||||||
|
(int val) => context.read<FormW4Cubit>().otherDependentsChanged(val),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (_totalCredits > 0) ...<Widget>[
|
if (_totalCredits(state) > 0) ...<Widget>[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
@@ -667,7 +668,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'\$${_totalCredits}',
|
'\$${_totalCredits(state)}',
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
@@ -682,8 +683,15 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCounter(String label, String badge, String key) {
|
Widget _buildCounter(
|
||||||
final int value = _formData[key] as int;
|
BuildContext context,
|
||||||
|
FormW4State state,
|
||||||
|
String label,
|
||||||
|
String badge,
|
||||||
|
int Function(FormW4State) getValue,
|
||||||
|
Function(int) onChanged,
|
||||||
|
) {
|
||||||
|
final int value = getValue(state);
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@@ -718,7 +726,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
_buildCircleBtn(
|
_buildCircleBtn(
|
||||||
UiIcons.minus,
|
UiIcons.minus,
|
||||||
() => _updateField(key, value > 0 ? value - 1 : 0),
|
() => onChanged(value > 0 ? value - 1 : 0),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 48,
|
width: 48,
|
||||||
@@ -733,7 +741,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
),
|
),
|
||||||
_buildCircleBtn(
|
_buildCircleBtn(
|
||||||
UiIcons.add,
|
UiIcons.add,
|
||||||
() => _updateField(key, value + 1),
|
() => onChanged(value + 1),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -757,7 +765,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStep5() {
|
Widget _buildStep5(BuildContext context, FormW4State state) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@@ -768,7 +776,8 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
const SizedBox(height: 24),
|
const SizedBox(height: 24),
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'4(a) Other income (not from jobs)',
|
'4(a) Other income (not from jobs)',
|
||||||
'otherIncome',
|
value: state.otherIncome,
|
||||||
|
onChanged: (String val) => context.read<FormW4Cubit>().otherIncomeChanged(val),
|
||||||
placeholder: '\$0',
|
placeholder: '\$0',
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
),
|
),
|
||||||
@@ -782,7 +791,8 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
|
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'4(b) Deductions',
|
'4(b) Deductions',
|
||||||
'deductions',
|
value: state.deductions,
|
||||||
|
onChanged: (String val) => context.read<FormW4Cubit>().deductionsChanged(val),
|
||||||
placeholder: '\$0',
|
placeholder: '\$0',
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
),
|
),
|
||||||
@@ -796,14 +806,15 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
|
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
'4(c) Extra withholding',
|
'4(c) Extra withholding',
|
||||||
'extraWithholding',
|
value: state.extraWithholding,
|
||||||
|
onChanged: (String val) => context.read<FormW4Cubit>().extraWithholdingChanged(val),
|
||||||
placeholder: '\$0',
|
placeholder: '\$0',
|
||||||
keyboardType: TextInputType.number,
|
keyboardType: TextInputType.number,
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 4, bottom: 16),
|
padding: const EdgeInsets.only(top: 4, bottom: 16),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Additional tax to withhold each pay period',
|
'Any additional tax you want withheld each pay period',
|
||||||
style: UiTypography.body3r.copyWith(color: UiColors.textSecondary),
|
style: UiTypography.body3r.copyWith(color: UiColors.textSecondary),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -811,7 +822,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildStep6() {
|
Widget _buildStep6(BuildContext context, FormW4State state) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@@ -832,20 +843,20 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
const SizedBox(height: 12),
|
const SizedBox(height: 12),
|
||||||
_buildSummaryRow(
|
_buildSummaryRow(
|
||||||
'Name',
|
'Name',
|
||||||
'${_formData['firstName']} ${_formData['lastName']}',
|
'${state.firstName} ${state.lastName}',
|
||||||
),
|
),
|
||||||
_buildSummaryRow(
|
_buildSummaryRow(
|
||||||
'SSN',
|
'SSN',
|
||||||
'***-**-${(_formData['ssn'] as String).length >= 4 ? (_formData['ssn'] as String).substring((_formData['ssn'] as String).length - 4) : '****'}',
|
'***-**-${state.ssn.length >= 4 ? state.ssn.substring(state.ssn.length - 4) : '****'}',
|
||||||
),
|
),
|
||||||
_buildSummaryRow(
|
_buildSummaryRow(
|
||||||
'Filing Status',
|
'Filing Status',
|
||||||
_getFilingStatusLabel(_formData['filingStatus']),
|
_getFilingStatusLabel(state.filingStatus),
|
||||||
),
|
),
|
||||||
if (_totalCredits > 0)
|
if (_totalCredits(state) > 0)
|
||||||
_buildSummaryRow(
|
_buildSummaryRow(
|
||||||
'Credits',
|
'Credits',
|
||||||
'\$${_totalCredits}',
|
'\$${_totalCredits(state)}',
|
||||||
valueColor: Colors.green[700],
|
valueColor: Colors.green[700],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -872,7 +883,11 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 6),
|
const SizedBox(height: 6),
|
||||||
TextField(
|
TextField(
|
||||||
onChanged: (String val) => setState(() => _signature = val),
|
controller: TextEditingController(text: state.signature)
|
||||||
|
..selection = TextSelection.fromPosition(
|
||||||
|
TextPosition(offset: state.signature.length),
|
||||||
|
),
|
||||||
|
onChanged: (String val) => context.read<FormW4Cubit>().signatureChanged(val),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: 'Type your full name',
|
hintText: 'Type your full name',
|
||||||
filled: true,
|
filled: true,
|
||||||
@@ -955,7 +970,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildFooter() {
|
Widget _buildFooter(BuildContext context, FormW4State state) {
|
||||||
return Container(
|
return Container(
|
||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
@@ -965,12 +980,12 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
if (_currentStep > 0)
|
if (state.currentStep > 0)
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(right: 12),
|
padding: const EdgeInsets.only(right: 12),
|
||||||
child: OutlinedButton(
|
child: OutlinedButton(
|
||||||
onPressed: _handleBack,
|
onPressed: () => _handleBack(context),
|
||||||
style: OutlinedButton.styleFrom(
|
style: OutlinedButton.styleFrom(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||||
side: const BorderSide(color: UiColors.border),
|
side: const BorderSide(color: UiColors.border),
|
||||||
@@ -995,8 +1010,8 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
onPressed: (_canProceed() && !_isSubmitting)
|
onPressed: (_canProceed(state) && state.status != FormW4Status.submitting)
|
||||||
? _handleNext
|
? () => _handleNext(context, state.currentStep)
|
||||||
: null,
|
: null,
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: UiColors.primary,
|
backgroundColor: UiColors.primary,
|
||||||
@@ -1008,7 +1023,7 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
),
|
),
|
||||||
elevation: 0,
|
elevation: 0,
|
||||||
),
|
),
|
||||||
child: _isSubmitting
|
child: state.status == FormW4Status.submitting
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
width: 20,
|
width: 20,
|
||||||
height: 20,
|
height: 20,
|
||||||
@@ -1021,11 +1036,11 @@ class _FormW4PageState extends State<FormW4Page> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
_currentStep == _steps.length - 1
|
state.currentStep == _steps.length - 1
|
||||||
? 'Submit Form'
|
? 'Submit Form'
|
||||||
: 'Continue',
|
: 'Continue',
|
||||||
),
|
),
|
||||||
if (_currentStep < _steps.length - 1) ...<Widget>[
|
if (state.currentStep < _steps.length - 1) ...<Widget>[
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
const Icon(UiIcons.arrowRight, size: 16, color: UiColors.bgPopup),
|
const Icon(UiIcons.arrowRight, size: 16, color: UiColors.bgPopup),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class TaxFormsPage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final cubit = Modular.get<TaxFormsCubit>();
|
final TaxFormsCubit cubit = Modular.get<TaxFormsCubit>();
|
||||||
|
|
||||||
if (cubit.state.status == TaxFormsStatus.initial) {
|
if (cubit.state.status == TaxFormsStatus.initial) {
|
||||||
cubit.loadTaxForms();
|
cubit.loadTaxForms();
|
||||||
|
|||||||
@@ -1,6 +1,10 @@
|
|||||||
|
import 'package:firebase_auth/firebase_auth.dart';
|
||||||
import 'package:flutter_modular/flutter_modular.dart';
|
import 'package:flutter_modular/flutter_modular.dart';
|
||||||
import 'data/repositories/tax_forms_repository_mock.dart';
|
import 'package:krow_data_connect/krow_data_connect.dart';
|
||||||
|
import 'data/repositories/tax_forms_repository_impl.dart';
|
||||||
import 'domain/repositories/tax_forms_repository.dart';
|
import 'domain/repositories/tax_forms_repository.dart';
|
||||||
|
import 'domain/usecases/get_tax_forms_usecase.dart';
|
||||||
|
import 'domain/usecases/submit_tax_form_usecase.dart';
|
||||||
import 'presentation/blocs/i9/form_i9_cubit.dart';
|
import 'presentation/blocs/i9/form_i9_cubit.dart';
|
||||||
import 'presentation/blocs/tax_forms/tax_forms_cubit.dart';
|
import 'presentation/blocs/tax_forms/tax_forms_cubit.dart';
|
||||||
import 'presentation/blocs/w4/form_w4_cubit.dart';
|
import 'presentation/blocs/w4/form_w4_cubit.dart';
|
||||||
@@ -11,7 +15,18 @@ import 'presentation/pages/tax_forms_page.dart';
|
|||||||
class StaffTaxFormsModule extends Module {
|
class StaffTaxFormsModule extends Module {
|
||||||
@override
|
@override
|
||||||
void binds(Injector i) {
|
void binds(Injector i) {
|
||||||
i.addLazySingleton<TaxFormsRepository>(TaxFormsRepositoryMock.new);
|
i.addLazySingleton<TaxFormsRepository>(
|
||||||
|
() => TaxFormsRepositoryImpl(
|
||||||
|
firebaseAuth: FirebaseAuth.instance,
|
||||||
|
dataConnect: ExampleConnector.instance,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use Cases
|
||||||
|
i.addLazySingleton(GetTaxFormsUseCase.new);
|
||||||
|
i.addLazySingleton(SubmitTaxFormUseCase.new);
|
||||||
|
|
||||||
|
// Blocs
|
||||||
i.addLazySingleton(TaxFormsCubit.new);
|
i.addLazySingleton(TaxFormsCubit.new);
|
||||||
i.addLazySingleton(FormI9Cubit.new);
|
i.addLazySingleton(FormI9Cubit.new);
|
||||||
i.addLazySingleton(FormW4Cubit.new);
|
i.addLazySingleton(FormW4Cubit.new);
|
||||||
|
|||||||
Reference in New Issue
Block a user