Fixed search field in payment page and cleaned up payment flow
This commit is contained in:
30
frontend/pweb/lib/pages/dashboard/payouts/widget.dart
Normal file
30
frontend/pweb/lib/pages/dashboard/payouts/widget.dart
Normal file
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/provider/payment/amount.dart';
|
||||
import 'package:pshared/provider/payment/flow.dart';
|
||||
import 'package:pshared/provider/payment/quotation.dart';
|
||||
import 'package:pshared/provider/payment/wallets.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/form.dart';
|
||||
|
||||
|
||||
class PaymentFromWrappingWidget extends StatelessWidget {
|
||||
const PaymentFromWrappingWidget({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => PaymentAmountProvider(),
|
||||
),
|
||||
ChangeNotifierProxyProvider4<OrganizationsProvider, PaymentAmountProvider, WalletsProvider, PaymentFlowProvider, QuotationProvider>(
|
||||
create: (_) => QuotationProvider(),
|
||||
update: (context, orgnization, payment, wallet, flow, provider) => provider!..update(orgnization, payment, wallet, flow),
|
||||
),
|
||||
],
|
||||
child: const PaymentFormWidget(),
|
||||
);
|
||||
}
|
||||
@@ -4,21 +4,17 @@ import 'package:collection/collection.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/models/payment/methods/data.dart';
|
||||
import 'package:pshared/models/payment/methods/type.dart';
|
||||
import 'package:pshared/models/payment/type.dart';
|
||||
import 'package:pshared/models/recipient/recipient.dart';
|
||||
import 'package:pshared/provider/organizations.dart';
|
||||
import 'package:pshared/provider/payment/amount.dart';
|
||||
import 'package:pshared/provider/payment/flow.dart';
|
||||
import 'package:pshared/provider/payment/provider.dart';
|
||||
import 'package:pshared/provider/payment/quotation.dart';
|
||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||
import 'package:pshared/provider/recipient/provider.dart';
|
||||
|
||||
import 'package:pshared/models/payment/wallet.dart';
|
||||
import 'package:pweb/pages/payment_methods/payment_page/body.dart';
|
||||
import 'package:pshared/provider/payment/wallets.dart';
|
||||
|
||||
|
||||
import 'package:pweb/pages/payment_methods/payment_page/body.dart';
|
||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||
import 'package:pweb/services/posthog.dart';
|
||||
|
||||
@@ -42,12 +38,16 @@ class PaymentPage extends StatefulWidget {
|
||||
class _PaymentPageState extends State<PaymentPage> {
|
||||
late final TextEditingController _searchController;
|
||||
late final FocusNode _searchFocusNode;
|
||||
late final PaymentFlowProvider _flowProvider;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_searchController = TextEditingController();
|
||||
_searchFocusNode = FocusNode();
|
||||
_flowProvider = PaymentFlowProvider(
|
||||
initialType: widget.initialPaymentType ?? PaymentType.bankAccount,
|
||||
);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _initializePaymentPage());
|
||||
}
|
||||
@@ -56,32 +56,47 @@ class _PaymentPageState extends State<PaymentPage> {
|
||||
void dispose() {
|
||||
_searchController.dispose();
|
||||
_searchFocusNode.dispose();
|
||||
_flowProvider.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _initializePaymentPage() {
|
||||
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||
_handleWalletAutoSelection(methodsProvider);
|
||||
|
||||
final recipient = context.read<RecipientsProvider>().currentObject;
|
||||
_flowProvider.syncWith(
|
||||
recipient: recipient,
|
||||
methodsProvider: methodsProvider,
|
||||
preferredType: widget.initialPaymentType,
|
||||
);
|
||||
}
|
||||
|
||||
void _handleSearchChanged(String query) {
|
||||
context.read<RecipientsProvider>().setQuery(query);
|
||||
}
|
||||
|
||||
void _handleRecipientSelected(BuildContext context, Recipient recipient) {
|
||||
void _handleRecipientSelected(Recipient recipient) {
|
||||
final recipientProvider = context.read<RecipientsProvider>();
|
||||
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||
|
||||
recipientProvider.setCurrentObject(recipient.id);
|
||||
_flowProvider.reset(
|
||||
recipient: recipient,
|
||||
methodsProvider: methodsProvider,
|
||||
preferredType: widget.initialPaymentType,
|
||||
);
|
||||
_clearSearchField();
|
||||
}
|
||||
|
||||
void _handleRecipientCleared(BuildContext context) {
|
||||
void _handleRecipientCleared() {
|
||||
final recipientProvider = context.read<RecipientsProvider>();
|
||||
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||
|
||||
recipientProvider.setCurrentObject(null);
|
||||
context.read<PaymentFlowProvider>().reset(
|
||||
_flowProvider.reset(
|
||||
recipient: null,
|
||||
availableTypes: _availablePaymentTypes(null, methodsProvider),
|
||||
methodsProvider: methodsProvider,
|
||||
preferredType: widget.initialPaymentType,
|
||||
);
|
||||
_clearSearchField();
|
||||
@@ -93,65 +108,39 @@ class _PaymentPageState extends State<PaymentPage> {
|
||||
context.read<RecipientsProvider>().setQuery('');
|
||||
}
|
||||
|
||||
void _handleSendPayment(BuildContext context) {
|
||||
if (context.read<QuotationProvider>().isReady) {
|
||||
context.read<PaymentProvider>().pay();
|
||||
PosthogService.paymentInitiated(
|
||||
method: context.read<PaymentFlowProvider>().selectedType,
|
||||
);
|
||||
}
|
||||
void _handleSendPayment() {
|
||||
// TODO: Handle Payment logic
|
||||
PosthogService.paymentInitiated(method: _flowProvider.selectedType);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final methodsProvider = context.watch<PaymentMethodsProvider>();
|
||||
final recipientProvider = context.watch<RecipientsProvider>();
|
||||
final recipient = recipientProvider.currentObject;
|
||||
final availableTypes = _availablePaymentTypes(recipient, methodsProvider);
|
||||
final recipientProvider = context.read<RecipientsProvider>();
|
||||
final recipient = context.select<RecipientsProvider, Recipient?>(
|
||||
(provider) => provider.currentObject,
|
||||
);
|
||||
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProxyProvider2<RecipientsProvider, PaymentMethodsProvider, PaymentFlowProvider>(
|
||||
create: (_) => PaymentFlowProvider(
|
||||
initialType: widget.initialPaymentType ?? PaymentType.bankAccount,
|
||||
),
|
||||
update: (_, recipients, methods, flow) {
|
||||
final currentRecipient = recipients.currentObject;
|
||||
flow!.sync(
|
||||
recipient: currentRecipient,
|
||||
availableTypes: _availablePaymentTypes(currentRecipient, methods),
|
||||
preferredType: currentRecipient != null ? widget.initialPaymentType : null,
|
||||
);
|
||||
return flow;
|
||||
},
|
||||
),
|
||||
ChangeNotifierProvider(
|
||||
create: (_) => PaymentAmountProvider(),
|
||||
),
|
||||
ChangeNotifierProxyProvider5<OrganizationsProvider, PaymentAmountProvider, WalletsProvider, PaymentFlowProvider, PaymentMethodsProvider, QuotationProvider>(
|
||||
create: (_) => QuotationProvider(),
|
||||
update: (_, organization, payment, wallet, flow, methods, provider) => provider!..update(organization, payment, wallet, flow, methods),
|
||||
),
|
||||
ChangeNotifierProxyProvider2<OrganizationsProvider, QuotationProvider, PaymentProvider>(
|
||||
create: (_) => PaymentProvider(),
|
||||
update: (_, organization, quotation, provider) => provider!..update(organization, quotation),
|
||||
),
|
||||
],
|
||||
child: Builder(
|
||||
builder: (innerContext) => PaymentPageBody(
|
||||
onBack: widget.onBack,
|
||||
fallbackDestination: widget.fallbackDestination,
|
||||
recipient: recipient,
|
||||
recipientProvider: recipientProvider,
|
||||
methodsProvider: methodsProvider,
|
||||
availablePaymentTypes: availableTypes,
|
||||
searchController: _searchController,
|
||||
searchFocusNode: _searchFocusNode,
|
||||
onSearchChanged: _handleSearchChanged,
|
||||
onRecipientSelected: (selected) => _handleRecipientSelected(innerContext, selected),
|
||||
onRecipientCleared: () => _handleRecipientCleared(innerContext),
|
||||
onSend: () => _handleSendPayment(innerContext),
|
||||
),
|
||||
_flowProvider.syncWith(
|
||||
recipient: recipient,
|
||||
methodsProvider: methodsProvider,
|
||||
preferredType: recipient != null ? widget.initialPaymentType : null,
|
||||
);
|
||||
|
||||
return ChangeNotifierProvider.value(
|
||||
value: _flowProvider,
|
||||
child: PaymentPageBody(
|
||||
onBack: widget.onBack,
|
||||
fallbackDestination: widget.fallbackDestination,
|
||||
recipient: recipient,
|
||||
recipientProvider: recipientProvider,
|
||||
methodsProvider: methodsProvider,
|
||||
searchController: _searchController,
|
||||
searchFocusNode: _searchFocusNode,
|
||||
onSearchChanged: _handleSearchChanged,
|
||||
onRecipientSelected: _handleRecipientSelected,
|
||||
onRecipientCleared: _handleRecipientCleared,
|
||||
onSend: _handleSendPayment,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -166,21 +155,6 @@ class _PaymentPageState extends State<PaymentPage> {
|
||||
}
|
||||
}
|
||||
|
||||
MethodMap _availablePaymentTypes(
|
||||
Recipient? recipient,
|
||||
PaymentMethodsProvider methodsProvider,
|
||||
) {
|
||||
if (recipient == null || !methodsProvider.isReady) return {};
|
||||
|
||||
final methodsForRecipient = methodsProvider.methods.where(
|
||||
(method) => !method.isArchived && method.recipientRef == recipient.id,
|
||||
);
|
||||
|
||||
return {
|
||||
for (final method in methodsForRecipient) method.type: method.data,
|
||||
};
|
||||
}
|
||||
|
||||
PaymentMethod? _getPaymentMethodForWallet(
|
||||
Wallet wallet,
|
||||
PaymentMethodsProvider methodsProvider,
|
||||
@@ -195,4 +169,4 @@ class _PaymentPageState extends State<PaymentPage> {
|
||||
(method.description?.contains(wallet.walletUserID) ?? false),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pshared/models/payment/methods/data.dart';
|
||||
import 'package:pshared/models/recipient/recipient.dart';
|
||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||
import 'package:pshared/provider/recipient/provider.dart';
|
||||
@@ -17,7 +16,6 @@ class PaymentPageBody extends StatelessWidget {
|
||||
final Recipient? recipient;
|
||||
final RecipientsProvider recipientProvider;
|
||||
final PaymentMethodsProvider methodsProvider;
|
||||
final MethodMap availablePaymentTypes;
|
||||
final PayoutDestination fallbackDestination;
|
||||
final TextEditingController searchController;
|
||||
final FocusNode searchFocusNode;
|
||||
@@ -32,7 +30,6 @@ class PaymentPageBody extends StatelessWidget {
|
||||
required this.recipient,
|
||||
required this.recipientProvider,
|
||||
required this.methodsProvider,
|
||||
required this.availablePaymentTypes,
|
||||
required this.fallbackDestination,
|
||||
required this.searchController,
|
||||
required this.searchFocusNode,
|
||||
@@ -61,7 +58,6 @@ class PaymentPageBody extends StatelessWidget {
|
||||
recipient: recipient,
|
||||
recipientProvider: recipientProvider,
|
||||
methodsProvider: methodsProvider,
|
||||
availablePaymentTypes: availablePaymentTypes,
|
||||
fallbackDestination: fallbackDestination,
|
||||
searchController: searchController,
|
||||
searchFocusNode: searchFocusNode,
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/models/payment/methods/data.dart';
|
||||
import 'package:pshared/models/recipient/recipient.dart';
|
||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||
import 'package:pshared/provider/recipient/provider.dart';
|
||||
import 'package:pshared/provider/payment/flow.dart';
|
||||
|
||||
import 'package:pweb/pages/payment_methods/payment_page/back_button.dart';
|
||||
import 'package:pweb/pages/payment_methods/payment_page/header.dart';
|
||||
@@ -27,7 +23,6 @@ class PaymentPageContent extends StatelessWidget {
|
||||
final Recipient? recipient;
|
||||
final RecipientsProvider recipientProvider;
|
||||
final PaymentMethodsProvider methodsProvider;
|
||||
final MethodMap availablePaymentTypes;
|
||||
final PayoutDestination fallbackDestination;
|
||||
final TextEditingController searchController;
|
||||
final FocusNode searchFocusNode;
|
||||
@@ -42,7 +37,6 @@ class PaymentPageContent extends StatelessWidget {
|
||||
required this.recipient,
|
||||
required this.recipientProvider,
|
||||
required this.methodsProvider,
|
||||
required this.availablePaymentTypes,
|
||||
required this.fallbackDestination,
|
||||
required this.searchController,
|
||||
required this.searchFocusNode,
|
||||
@@ -55,7 +49,6 @@ class PaymentPageContent extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final dimensions = AppDimensions();
|
||||
final flowProvider = context.watch<PaymentFlowProvider>();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
return Align(
|
||||
@@ -98,12 +91,7 @@ class PaymentPageContent extends StatelessWidget {
|
||||
onRecipientCleared: onRecipientCleared,
|
||||
),
|
||||
SizedBox(height: dimensions.paddingXLarge),
|
||||
PaymentInfoSection(
|
||||
dimensions: dimensions,
|
||||
flowProvider: flowProvider,
|
||||
recipient: recipient,
|
||||
availableTypes: availablePaymentTypes,
|
||||
),
|
||||
PaymentInfoSection(dimensions: dimensions),
|
||||
SizedBox(height: dimensions.paddingLarge),
|
||||
const PaymentFormWidget(),
|
||||
SizedBox(height: dimensions.paddingXXXLarge),
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/models/payment/methods/data.dart';
|
||||
import 'package:pshared/models/recipient/recipient.dart';
|
||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||
import 'package:pshared/provider/recipient/provider.dart';
|
||||
import 'package:pshared/provider/payment/flow.dart';
|
||||
|
||||
import 'package:pweb/pages/dashboard/payouts/form.dart';
|
||||
import 'package:pweb/pages/dashboard/payouts/widget.dart';
|
||||
import 'package:pweb/pages/payment_methods/payment_page/back_button.dart';
|
||||
import 'package:pweb/pages/payment_methods/payment_page/header.dart';
|
||||
import 'package:pweb/pages/payment_methods/payment_page/method_selector.dart';
|
||||
@@ -27,7 +23,6 @@ class PaymentPageContent extends StatelessWidget {
|
||||
final Recipient? recipient;
|
||||
final RecipientsProvider recipientProvider;
|
||||
final PaymentMethodsProvider methodsProvider;
|
||||
final MethodMap availablePaymentTypes;
|
||||
final PayoutDestination fallbackDestination;
|
||||
final TextEditingController searchController;
|
||||
final FocusNode searchFocusNode;
|
||||
@@ -42,7 +37,6 @@ class PaymentPageContent extends StatelessWidget {
|
||||
required this.recipient,
|
||||
required this.recipientProvider,
|
||||
required this.methodsProvider,
|
||||
required this.availablePaymentTypes,
|
||||
required this.fallbackDestination,
|
||||
required this.searchController,
|
||||
required this.searchFocusNode,
|
||||
@@ -55,7 +49,6 @@ class PaymentPageContent extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final dimensions = AppDimensions();
|
||||
final flowProvider = context.watch<PaymentFlowProvider>();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
return Align(
|
||||
@@ -98,14 +91,9 @@ class PaymentPageContent extends StatelessWidget {
|
||||
onRecipientCleared: onRecipientCleared,
|
||||
),
|
||||
SizedBox(height: dimensions.paddingXLarge),
|
||||
PaymentInfoSection(
|
||||
dimensions: dimensions,
|
||||
flowProvider: flowProvider,
|
||||
recipient: recipient,
|
||||
availableTypes: availablePaymentTypes,
|
||||
),
|
||||
PaymentInfoSection(dimensions: dimensions),
|
||||
SizedBox(height: dimensions.paddingLarge),
|
||||
const PaymentFormWidget(),
|
||||
const PaymentFromWrappingWidget(),
|
||||
SizedBox(height: dimensions.paddingXXXLarge),
|
||||
SendButton(onPressed: onSend),
|
||||
SizedBox(height: dimensions.paddingLarge),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/models/payment/methods/data.dart';
|
||||
import 'package:pshared/models/payment/type.dart';
|
||||
import 'package:pshared/models/recipient/recipient.dart';
|
||||
import 'package:pshared/provider/payment/flow.dart';
|
||||
|
||||
import 'package:pweb/pages/payment_methods/form.dart';
|
||||
@@ -15,25 +15,18 @@ import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
class PaymentInfoSection extends StatelessWidget {
|
||||
final AppDimensions dimensions;
|
||||
final MethodMap availableTypes;
|
||||
final PaymentFlowProvider flowProvider;
|
||||
final Recipient? recipient;
|
||||
|
||||
const PaymentInfoSection({
|
||||
super.key,
|
||||
required this.dimensions,
|
||||
required this.availableTypes,
|
||||
required this.flowProvider,
|
||||
required this.recipient,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final hasRecipient = recipient != null;
|
||||
final MethodMap resolvedAvailableTypes = hasRecipient
|
||||
? availableTypes
|
||||
: {for (final type in PaymentType.values) type: null};
|
||||
final flowProvider = context.watch<PaymentFlowProvider>();
|
||||
final hasRecipient = flowProvider.hasRecipient;
|
||||
final MethodMap resolvedAvailableTypes = flowProvider.availableTypes;
|
||||
|
||||
if (hasRecipient && resolvedAvailableTypes.isEmpty) {
|
||||
return Text(loc.recipientNoPaymentDetails);
|
||||
@@ -62,7 +55,7 @@ class PaymentInfoSection extends StatelessWidget {
|
||||
flowProvider.setManualPaymentData(data);
|
||||
}
|
||||
},
|
||||
initialData: hasRecipient ? resolvedAvailableTypes[selectedType] : flowProvider.manualPaymentData,
|
||||
initialData: flowProvider.selectedPaymentData,
|
||||
isEditable: !hasRecipient,
|
||||
),
|
||||
],
|
||||
|
||||
@@ -45,25 +45,44 @@ class RecipientSection extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SectionTitle(loc.recipient),
|
||||
SizedBox(height: dimensions.paddingSmall),
|
||||
RecipientSearchField(
|
||||
controller: searchController,
|
||||
onChanged: onSearchChanged,
|
||||
focusNode: searchFocusNode,
|
||||
),
|
||||
if (recipientProvider.query.isNotEmpty) ...[
|
||||
SizedBox(height: dimensions.paddingMedium),
|
||||
RecipientSearchResults(
|
||||
dimensions: dimensions,
|
||||
recipientProvider: recipientProvider,
|
||||
onRecipientSelected: onRecipientSelected,
|
||||
),
|
||||
],
|
||||
],
|
||||
return AnimatedBuilder(
|
||||
animation: recipientProvider,
|
||||
builder: (context, _) {
|
||||
final previousRecipient = recipientProvider.previousRecipient;
|
||||
final hasQuery = recipientProvider.query.isNotEmpty;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SectionTitle(loc.recipient),
|
||||
SizedBox(height: dimensions.paddingSmall),
|
||||
RecipientSearchField(
|
||||
controller: searchController,
|
||||
onChanged: onSearchChanged,
|
||||
focusNode: searchFocusNode,
|
||||
),
|
||||
if (previousRecipient != null) ...[
|
||||
SizedBox(height: dimensions.paddingSmall),
|
||||
ListTile(
|
||||
dense: true,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
leading: const Icon(Icons.undo),
|
||||
title: Text(loc.back),
|
||||
subtitle: Text(previousRecipient.name),
|
||||
onTap: () => onRecipientSelected(previousRecipient),
|
||||
),
|
||||
],
|
||||
if (hasQuery) ...[
|
||||
SizedBox(height: dimensions.paddingMedium),
|
||||
RecipientSearchResults(
|
||||
dimensions: dimensions,
|
||||
recipientProvider: recipientProvider,
|
||||
onRecipientSelected: onRecipientSelected,
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user