Frontend first draft

This commit is contained in:
Arseni
2025-11-13 15:06:15 +03:00
parent e47f343afb
commit ddb54ddfdc
504 changed files with 25498 additions and 1 deletions

View File

@@ -0,0 +1,232 @@
import 'package:amplitude_flutter/amplitude.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pweb/pages/dashboard/payouts/payment_form.dart';
import 'package:pweb/pages/dashboard/payouts/single/form/details.dart';
import 'package:pweb/pages/dashboard/payouts/single/form/header.dart';
import 'package:pweb/providers/payment_methods.dart';
import 'package:pweb/providers/recipient.dart';
import 'package:pweb/services/amplitude.dart';
import 'package:pweb/utils/dimensions.dart';
import 'package:pweb/utils/payment/dropdown.dart';
import 'package:pweb/utils/payment/selector_type.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
import 'package:pweb/widgets/sidebar/destinations.dart';
//TODO: decide whether to make AppDimensions universal for the whole app or leave it as it is - unique for this page alone
class PaymentPage extends StatefulWidget {
final PaymentType? type;
final ValueChanged<Recipient?>? onBack;
const PaymentPage({super.key, this.type, this.onBack});
@override
State<PaymentPage> createState() => _PaymentPageState();
}
class _PaymentPageState extends State<PaymentPage> {
late Map<PaymentType, Object> _availableTypes;
late PaymentType _selectedType;
bool _isFormVisible = false;
@override
void didChangeDependencies() {
super.didChangeDependencies();
final recipientProvider = context.watch<RecipientProvider>();
final methodsProvider = context.watch<PaymentMethodsProvider>();
final recipient = recipientProvider.selectedRecipient;
// Initialize available types based on whether we have a recipient
if (recipient != null) {
// We have a recipient - use their payment methods
_availableTypes = {
if (recipient.card != null) PaymentType.card: recipient.card!,
if (recipient.iban != null) PaymentType.iban: recipient.iban!,
if (recipient.wallet != null) PaymentType.wallet: recipient.wallet!,
if (recipient.bank != null) PaymentType.bankAccount: recipient.bank!,
};
// Set selected type if it's available, otherwise use first available type
if (_availableTypes.containsKey(_selectedType)) {
// Keep current selection if valid
} else if (_availableTypes.isNotEmpty) {
_selectedType = _availableTypes.keys.first;
} else {
// Fallback if recipient has no payment methods
_selectedType = PaymentType.bankAccount;
}
} else {
// No recipient - we're creating a new payment from scratch
_availableTypes = {};
_selectedType = widget.type ?? PaymentType.bankAccount;
_isFormVisible = true; // Always show form when creating new payment
}
// Load payment methods if not already loaded
if (methodsProvider.methods.isEmpty && !methodsProvider.isLoading) {
WidgetsBinding.instance.addPostFrameCallback((_) {
methodsProvider.loadMethods();
});
}
}
@override
void initState() {
super.initState();
// Initial values
_availableTypes = {};
_selectedType = widget.type ?? PaymentType.bankAccount;
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final dimensions = AppDimensions();
final recipientProvider = context.watch<RecipientProvider>();
final methodsProvider = context.watch<PaymentMethodsProvider>();
final recipient = recipientProvider.selectedRecipient;
// Show loading state for payment methods
if (methodsProvider.isLoading) {
return const Center(child: CircularProgressIndicator());
}
// Show error state for payment methods
if (methodsProvider.error != null) {
return Center(
child: Text('Error: ${methodsProvider.error}'),
);
}
return Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: dimensions.maxContentWidth),
child: Material(
elevation: dimensions.elevationSmall,
borderRadius: BorderRadius.circular(dimensions.borderRadiusMedium),
color: theme.colorScheme.onSecondary,
child: Padding(
padding: EdgeInsets.all(dimensions.paddingLarge),
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Back button
Align(
alignment: Alignment.topLeft,
child: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
widget.onBack?.call(recipient);
},
),
),
SizedBox(height: dimensions.paddingSmall),
// Header
Row(
children: [
Icon(
Icons.send_rounded,
color: theme.colorScheme.primary,
size: dimensions.iconSizeLarge
),
SizedBox(width: dimensions.spacingSmall),
Text(
AppLocalizations.of(context)!.sendTo,
style: theme.textTheme.headlineSmall?.copyWith(
fontWeight: FontWeight.bold
),
),
],
),
SizedBox(height: dimensions.paddingXXLarge),
// Payment method dropdown (user's payment methods)
PaymentMethodDropdown(
methods: methodsProvider.methods,
initialValue: methodsProvider.selectedMethod,
onChanged: (method) {
methodsProvider.selectMethod(method);
},
),
SizedBox(height: dimensions.paddingXLarge),
// Recipient section (only show if we have a recipient)
if (recipient != null) ...[
RecipientHeader(recipient: recipient),
SizedBox(height: dimensions.paddingMedium),
// Payment type selector (recipient's payment methods)
if (_availableTypes.isNotEmpty)
PaymentTypeSelector(
availableTypes: _availableTypes,
selectedType: _selectedType,
onSelected: (type) => setState(() => _selectedType = type),
),
SizedBox(height: dimensions.paddingMedium),
],
// Payment details section
PaymentDetailsSection(
isFormVisible: recipient == null || _isFormVisible,
onToggle: recipient != null
? () => setState(() => _isFormVisible = !_isFormVisible)
: null, // No toggle when creating new payment
selectedType: _selectedType,
data: _availableTypes[_selectedType],
isEditable: recipient == null,
),
const PaymentFormWidget(),
SizedBox(height: dimensions.paddingXXXLarge),
Center(
child: SizedBox(
width: dimensions.buttonWidth,
height: dimensions.buttonHeight,
child: InkWell(
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
onTap: () =>
// TODO: Handle Payment logic
AmplitudeService.pageOpened(PayoutDestination.payment), //TODO: replace with payment event
child: Container(
decoration: BoxDecoration(
color: theme.colorScheme.primary,
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
),
child: Center(
child: Text(
AppLocalizations.of(context)!.send,
style: theme.textTheme.bodyLarge?.copyWith(
color: theme.colorScheme.onSecondary,
fontWeight: FontWeight.w600,
),
),
),
),
),
),
),
SizedBox(height: dimensions.paddingLarge),
],
),
),
),
),
),
);
}
}