From 0ce90eef219e677ed30db27c80e0265a1d3b053a Mon Sep 17 00:00:00 2001 From: Arseni Date: Thu, 5 Feb 2026 02:42:00 +0300 Subject: [PATCH 1/5] name ui fix and removed parts of the app that are not ready --- frontend/pweb/lib/l10n/en.arb | 2 +- frontend/pweb/lib/l10n/ru.arb | 2 +- .../pweb/lib/pages/dashboard/dashboard.dart | 21 +++--- .../profile/account/name/editing.dart | 73 +++++++++++++++++++ .../settings/profile/account/name/line.dart | 24 ++++++ .../settings/profile/account/name/text.dart | 65 ++++------------- .../settings/profile/account/name/view.dart | 73 +++++++++++++++++++ .../pweb/lib/widgets/sidebar/sidebar.dart | 3 +- frontend/pweb/lib/widgets/sidebar/user.dart | 2 +- 9 files changed, 199 insertions(+), 66 deletions(-) create mode 100644 frontend/pweb/lib/pages/settings/profile/account/name/editing.dart create mode 100644 frontend/pweb/lib/pages/settings/profile/account/name/line.dart create mode 100644 frontend/pweb/lib/pages/settings/profile/account/name/view.dart diff --git a/frontend/pweb/lib/l10n/en.arb b/frontend/pweb/lib/l10n/en.arb index 512ecbc1..f8bff51f 100644 --- a/frontend/pweb/lib/l10n/en.arb +++ b/frontend/pweb/lib/l10n/en.arb @@ -128,7 +128,7 @@ "payoutNavReports": "Reports", "payoutNavSettings": "Settings", "payoutNavLogout": "Logout", - "payoutNavMethods": "Payouts", + "payoutNavMethods": "Payout Methods", "expand": "Expand", "collapse": "Collapse", "pageTitleRecipients": "Recipient address book", diff --git a/frontend/pweb/lib/l10n/ru.arb b/frontend/pweb/lib/l10n/ru.arb index e6709cf4..28578227 100644 --- a/frontend/pweb/lib/l10n/ru.arb +++ b/frontend/pweb/lib/l10n/ru.arb @@ -128,7 +128,7 @@ "payoutNavReports": "Отчеты", "payoutNavSettings": "Настройки", "payoutNavLogout": "Выйти", - "payoutNavMethods": "Выплаты", + "payoutNavMethods": "Способы выплат", "expand": "Развернуть", "collapse": "Свернуть", "pageTitleRecipients": "Адресная книга получателей", diff --git a/frontend/pweb/lib/pages/dashboard/dashboard.dart b/frontend/pweb/lib/pages/dashboard/dashboard.dart index a348c539..b7e044c5 100644 --- a/frontend/pweb/lib/pages/dashboard/dashboard.dart +++ b/frontend/pweb/lib/pages/dashboard/dashboard.dart @@ -70,16 +70,17 @@ class _DashboardPageState extends State { icon: Icons.person_add, ), ), - const SizedBox(width: AppSpacing.small), - Expanded( - flex: 0, - child: TransactionRefButton( - onTap: () => _setActive(false), - isActive: _showContainerMultiple, - label: l10n.sendMultiple, - icon: Icons.group_add, - ), - ), + //TODO bring back multiple payouts + // const SizedBox(width: AppSpacing.small), + // Expanded( + // flex: 0, + // child: TransactionRefButton( + // onTap: () => _setActive(false), + // isActive: _showContainerMultiple, + // label: l10n.sendMultiple, + // icon: Icons.group_add, + // ), + // ), ], ), const SizedBox(height: AppSpacing.medium), diff --git a/frontend/pweb/lib/pages/settings/profile/account/name/editing.dart b/frontend/pweb/lib/pages/settings/profile/account/name/editing.dart new file mode 100644 index 00000000..57046f70 --- /dev/null +++ b/frontend/pweb/lib/pages/settings/profile/account/name/editing.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; + +import 'package:pweb/providers/account_name.dart'; + + +class AccountNameEditingFields extends StatelessWidget { + const AccountNameEditingFields({ + super.key, + required this.hintText, + required this.lastNameHint, + required this.inputWidth, + required this.borderWidth, + required this.state, + }); + + final String hintText; + final String lastNameHint; + final double inputWidth; + final double borderWidth; + final AccountNameState state; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return SizedBox( + width: inputWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + controller: state.firstNameController, + style: theme.textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + ), + autofocus: true, + enabled: !state.isBusy, + decoration: InputDecoration( + hintText: hintText, + labelText: hintText, + isDense: true, + border: UnderlineInputBorder( + borderSide: BorderSide( + color: theme.colorScheme.primary, + width: borderWidth, + ), + ), + ), + ), + const SizedBox(height: 8), + TextFormField( + controller: state.lastNameController, + style: theme.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w600, + ), + enabled: !state.isBusy, + decoration: InputDecoration( + hintText: lastNameHint, + labelText: lastNameHint, + isDense: true, + border: UnderlineInputBorder( + borderSide: BorderSide( + color: theme.colorScheme.primary, + width: borderWidth, + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/frontend/pweb/lib/pages/settings/profile/account/name/line.dart b/frontend/pweb/lib/pages/settings/profile/account/name/line.dart new file mode 100644 index 00000000..6de2ad64 --- /dev/null +++ b/frontend/pweb/lib/pages/settings/profile/account/name/line.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + + +class AccountNameSingleLineText extends StatelessWidget { + const AccountNameSingleLineText({ + super.key, + required this.text, + required this.style, + }); + + final String text; + final TextStyle? style; + + @override + Widget build(BuildContext context) { + return Text( + text, + maxLines: 1, + softWrap: false, + overflow: TextOverflow.ellipsis, + style: style, + ); + } +} diff --git a/frontend/pweb/lib/pages/settings/profile/account/name/text.dart b/frontend/pweb/lib/pages/settings/profile/account/name/text.dart index 566c959f..7fd154fd 100644 --- a/frontend/pweb/lib/pages/settings/profile/account/name/text.dart +++ b/frontend/pweb/lib/pages/settings/profile/account/name/text.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:pweb/pages/settings/profile/account/name/editing.dart'; +import 'package:pweb/pages/settings/profile/account/name/view.dart'; import 'package:pweb/providers/account_name.dart'; @@ -22,63 +24,22 @@ class AccountNameText extends StatelessWidget { @override Widget build(BuildContext context) { final state = context.watch(); - final theme = Theme.of(context); if (state.isEditing) { - return SizedBox( - width: inputWidth, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - controller: state.firstNameController, - style: theme.textTheme.headlineMedium?.copyWith( - fontWeight: FontWeight.bold, - ), - autofocus: true, - enabled: !state.isBusy, - decoration: InputDecoration( - hintText: hintText, - labelText: hintText, - isDense: true, - border: UnderlineInputBorder( - borderSide: BorderSide( - color: theme.colorScheme.primary, - width: borderWidth, - ), - ), - ), - ), - const SizedBox(height: 8), - TextFormField( - controller: state.lastNameController, - style: theme.textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.w600, - ), - enabled: !state.isBusy, - decoration: InputDecoration( - hintText: lastNameHint, - labelText: lastNameHint, - isDense: true, - border: UnderlineInputBorder( - borderSide: BorderSide( - color: theme.colorScheme.primary, - width: borderWidth, - ), - ), - ), - ), - ], - ), + return AccountNameEditingFields( + hintText: hintText, + lastNameHint: lastNameHint, + borderWidth: borderWidth, + inputWidth: inputWidth, + state: state, ); } - final displayName = state.currentFullName.isNotEmpty ? state.currentFullName : hintText; - return Text( - displayName, - style: theme.textTheme.headlineMedium?.copyWith( - fontWeight: FontWeight.bold, - ), + return AccountNameViewText( + hintText: hintText, + inputWidth: inputWidth, + firstName: state.currentFirstName, + lastName: state.currentLastName, ); } } diff --git a/frontend/pweb/lib/pages/settings/profile/account/name/view.dart b/frontend/pweb/lib/pages/settings/profile/account/name/view.dart new file mode 100644 index 00000000..ca090c8f --- /dev/null +++ b/frontend/pweb/lib/pages/settings/profile/account/name/view.dart @@ -0,0 +1,73 @@ +import 'package:flutter/material.dart'; + +import 'package:pweb/pages/settings/profile/account/name/line.dart'; + + +class AccountNameViewText extends StatelessWidget { + const AccountNameViewText({ + super.key, + required this.hintText, + required this.inputWidth, + required this.firstName, + required this.lastName, + }); + + final String hintText; + final double inputWidth; + final String firstName; + final String lastName; + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final trimmedFirstName = firstName.trim(); + final trimmedLastName = lastName.trim(); + final hasFirstName = trimmedFirstName.isNotEmpty; + final hasLastName = trimmedLastName.isNotEmpty; + + final firstLineStyle = theme.textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + ); + final secondLineStyle = theme.textTheme.headlineMedium?.copyWith( + fontWeight: FontWeight.bold, + ); + + if (!hasFirstName && !hasLastName) { + return SizedBox( + width: inputWidth, + child: AccountNameSingleLineText( + text: hintText, + style: firstLineStyle, + ), + ); + } + + if (!hasFirstName || !hasLastName) { + final singleLineName = hasFirstName ? trimmedFirstName : trimmedLastName; + return SizedBox( + width: inputWidth, + child: AccountNameSingleLineText( + text: singleLineName, + style: firstLineStyle, + ), + ); + } + + return SizedBox( + width: inputWidth, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + AccountNameSingleLineText( + text: trimmedFirstName, + style: firstLineStyle, + ), + AccountNameSingleLineText( + text: trimmedLastName, + style: secondLineStyle, + ), + ], + ), + ); + } +} diff --git a/frontend/pweb/lib/widgets/sidebar/sidebar.dart b/frontend/pweb/lib/widgets/sidebar/sidebar.dart index b3e354ac..1cef014c 100644 --- a/frontend/pweb/lib/widgets/sidebar/sidebar.dart +++ b/frontend/pweb/lib/widgets/sidebar/sidebar.dart @@ -46,7 +46,8 @@ class PayoutSidebar extends StatelessWidget { PayoutDestination.recipients, PayoutDestination.invitations, PayoutDestination.methods, - PayoutDestination.reports, + //PayoutDestination.reports, + //TODO Add when ready ]; final theme = Theme.of(context); diff --git a/frontend/pweb/lib/widgets/sidebar/user.dart b/frontend/pweb/lib/widgets/sidebar/user.dart index 0370bcf9..a2a7ef9a 100644 --- a/frontend/pweb/lib/widgets/sidebar/user.dart +++ b/frontend/pweb/lib/widgets/sidebar/user.dart @@ -57,7 +57,7 @@ class UserProfileCard extends StatelessWidget { child: Text( userName ?? loc.userNamePlaceholder, style: theme.textTheme.bodyLarge?.copyWith( - fontSize: 20, + fontSize: 18, fontWeight: FontWeight.w500, ), maxLines: 2, -- 2.49.1 From 81ffdd42911df963523cfcaeb3d44c816cb1e5f6 Mon Sep 17 00:00:00 2001 From: Arseni Date: Thu, 5 Feb 2026 02:54:13 +0300 Subject: [PATCH 2/5] sneaky email verification fix --- .../lib/provider/email_verification.dart | 17 ++- frontend/pweb/lib/pages/errors/error.dart | 37 ++++-- .../pweb/lib/pages/verification/content.dart | 111 ++++++++++++++++++ .../lib/pages/verification/controller.dart | 64 ++++++++++ .../pweb/lib/pages/verification/page.dart | 77 ++---------- .../lib/pages/verification/resend_dialog.dart | 41 +++++++ 6 files changed, 266 insertions(+), 81 deletions(-) create mode 100644 frontend/pweb/lib/pages/verification/content.dart create mode 100644 frontend/pweb/lib/pages/verification/controller.dart create mode 100644 frontend/pweb/lib/pages/verification/resend_dialog.dart diff --git a/frontend/pshared/lib/provider/email_verification.dart b/frontend/pshared/lib/provider/email_verification.dart index 3d1f21f0..067aaf89 100644 --- a/frontend/pshared/lib/provider/email_verification.dart +++ b/frontend/pshared/lib/provider/email_verification.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:pshared/api/responses/error/server.dart'; import 'package:pshared/provider/resource.dart'; import 'package:pshared/service/account.dart'; import 'package:pshared/utils/exception.dart'; - class EmailVerificationProvider extends ChangeNotifier { Resource _resource = Resource(data: null, isLoading: false); String? _token; @@ -13,6 +13,11 @@ class EmailVerificationProvider extends ChangeNotifier { bool get isLoading => _resource.isLoading; bool get isSuccess => _resource.data == true; Exception? get error => _resource.error; + int? get errorCode => _resource.error is ErrorResponse + ? (_resource.error as ErrorResponse).code + : null; + bool get canResendVerification => + errorCode == 400 || errorCode == 410 || errorCode == 500; Future verify(String token) async { final trimmed = token.trim(); @@ -33,12 +38,12 @@ class EmailVerificationProvider extends ChangeNotifier { await AccountService.verifyEmail(trimmed); _setResource(Resource(data: true, isLoading: false)); } catch (e) { + if (e is ErrorResponse && e.code == 404) { + _setResource(Resource(data: true, isLoading: false)); + return; + } _setResource( - Resource( - data: null, - isLoading: false, - error: toException(e), - ), + Resource(data: null, isLoading: false, error: toException(e)), ); } } diff --git a/frontend/pweb/lib/pages/errors/error.dart b/frontend/pweb/lib/pages/errors/error.dart index 3f898d15..36135742 100644 --- a/frontend/pweb/lib/pages/errors/error.dart +++ b/frontend/pweb/lib/pages/errors/error.dart @@ -11,12 +11,14 @@ class ErrorPage extends StatelessWidget { final String title; final String errorMessage; final String errorHint; + final Widget? action; - const ErrorPage({ - super.key, + const ErrorPage({ + super.key, required this.title, - required this.errorMessage, + required this.errorMessage, required this.errorHint, + this.action, }); @override @@ -26,19 +28,34 @@ class ErrorPage extends StatelessWidget { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Icon(Icons.error_outline, size: 72, color: Theme.of(context).colorScheme.error), + Icon( + Icons.error_outline, + size: 72, + color: Theme.of(context).colorScheme.error, + ), const VSpacer(), Text( title, - style: Theme.of(context).textTheme.titleLarge?.copyWith(color: Theme.of(context).colorScheme.error), + style: Theme.of(context).textTheme.titleLarge?.copyWith( + color: Theme.of(context).colorScheme.error, + ), textAlign: TextAlign.center, ), const VSpacer(multiplier: 0.5), ListTile( - title: Text(errorMessage, textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleLarge), - subtitle: Text(errorHint, textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodySmall), + title: Text( + errorMessage, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleLarge, + ), + subtitle: Text( + errorHint, + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.bodySmall, + ), ), const VSpacer(multiplier: 1.5), + if (action != null) ...[action!, const VSpacer(multiplier: 0.5)], TextButton( onPressed: () => navigate(context, Pages.root), child: Text(AppLocalizations.of(context)!.goToMainPage), @@ -54,8 +71,10 @@ Widget exceptionToErrorPage({ required String title, required String errorMessage, required Object exception, + Widget? action, }) => ErrorPage( - title: title, - errorMessage: errorMessage, + title: title, + errorMessage: errorMessage, errorHint: ErrorHandler.handleError(context, exception), + action: action, ); diff --git a/frontend/pweb/lib/pages/verification/content.dart b/frontend/pweb/lib/pages/verification/content.dart new file mode 100644 index 00000000..b5c82f88 --- /dev/null +++ b/frontend/pweb/lib/pages/verification/content.dart @@ -0,0 +1,111 @@ +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import 'package:pshared/widgets/locale.dart'; + +import 'package:pweb/app/router/pages.dart'; +import 'package:pweb/pages/errors/error.dart'; +import 'package:pweb/pages/status/success.dart'; +import 'package:pweb/pages/with_footer.dart'; +import 'package:pweb/pages/verification/controller.dart'; +import 'package:pweb/pages/verification/resend_dialog.dart'; +import 'package:pweb/utils/snackbar.dart'; +import 'package:pweb/widgets/error/snackbar.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +class AccountVerificationContent extends StatefulWidget { + const AccountVerificationContent(); + + @override + State createState() => + AccountVerificationContentState(); +} + +class AccountVerificationContentState + extends State { + Future _resendVerificationEmail() async { + final controller = context.read(); + if (controller.isResending) return; + final locs = AppLocalizations.of(context)!; + final email = await requestVerificationEmail(context, locs); + if (!mounted || email == null) return; + if (email.isEmpty) { + notifyUser(context, locs.errorEmailMissing); + return; + } + + try { + await controller.resendVerificationEmail(email); + if (!mounted) return; + await notifyUser(context, locs.signupConfirmationResent(email)); + } catch (e) { + if (!mounted) return; + await postNotifyUserOfErrorX( + context: context, + errorSituation: locs.signupConfirmationResendError, + exception: e, + ); + } + } + + @override + Widget build(BuildContext context) { + final locs = AppLocalizations.of(context)!; + final controller = context.watch(); + final action = OutlinedButton.icon( + onPressed: () => navigateAndReplace(context, Pages.login), + icon: const Icon(Icons.login), + label: Text(locs.login), + ); + + Widget content; + if (controller.isLoading) { + content = const Center(child: CircularProgressIndicator()); + } else if (controller.isSuccess) { + content = StatusPageSuccess( + successMessage: locs.accountVerified, + successDescription: locs.accountVerifiedDescription, + action: action, + ); + } else { + content = exceptionToErrorPage( + context: context, + title: locs.verificationFailed, + errorMessage: locs.accountVerificationFailed, + exception: + controller.error ?? Exception(locs.accountVerificationFailed), + action: controller.canResend + ? OutlinedButton.icon( + onPressed: controller.isResending + ? null + : _resendVerificationEmail, + icon: controller.isResending + ? const SizedBox( + width: 18, + height: 18, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Icon(Icons.mark_email_unread_outlined), + label: Text(locs.signupConfirmationResend), + ) + : null, + ); + } + + return PageWithFooter( + appBar: AppBar( + title: Text(locs.verifyAccount), + centerTitle: true, + actions: [ + const LocaleChangerDropdown( + availableLocales: AppLocalizations.supportedLocales, + ), + ], + ), + child: content, + ); + } +} \ No newline at end of file diff --git a/frontend/pweb/lib/pages/verification/controller.dart b/frontend/pweb/lib/pages/verification/controller.dart new file mode 100644 index 00000000..c6dc3b3d --- /dev/null +++ b/frontend/pweb/lib/pages/verification/controller.dart @@ -0,0 +1,64 @@ +import 'package:flutter/material.dart'; + +import 'package:pshared/provider/account.dart'; +import 'package:pshared/provider/email_verification.dart'; + +import 'package:pweb/models/flow_status.dart'; + + +class AccountVerificationController extends ChangeNotifier { + AccountVerificationController({ + required AccountProvider accountProvider, + required EmailVerificationProvider verificationProvider, + }) : _accountProvider = accountProvider, + _verificationProvider = verificationProvider { + _verificationProvider.addListener(_onVerificationChanged); + } + + final AccountProvider _accountProvider; + final EmailVerificationProvider _verificationProvider; + + FlowStatus _resendStatus = FlowStatus.idle; + String? _verificationToken; + + bool get isLoading => _verificationProvider.isLoading; + bool get isSuccess => _verificationProvider.isSuccess; + Exception? get error => _verificationProvider.error; + bool get canResend => _verificationProvider.canResendVerification; + bool get isResending => _resendStatus == FlowStatus.resending; + + void startVerification(String token) { + final trimmed = token.trim(); + if (trimmed.isEmpty || trimmed == _verificationToken) return; + _verificationToken = trimmed; + _verificationProvider.verify(trimmed); + } + + Future resendVerificationEmail(String email) async { + final trimmed = email.trim(); + if (trimmed.isEmpty || isResending) return; + _setResendStatus(FlowStatus.resending); + try { + await _accountProvider.resendVerificationEmail(trimmed); + _setResendStatus(FlowStatus.idle); + } catch (_) { + _setResendStatus(FlowStatus.error); + rethrow; + } + } + + void _onVerificationChanged() { + notifyListeners(); + } + + void _setResendStatus(FlowStatus status) { + _resendStatus = status; + notifyListeners(); + } + + @override + void dispose() { + _verificationProvider.removeListener(_onVerificationChanged); + super.dispose(); + } +} diff --git a/frontend/pweb/lib/pages/verification/page.dart b/frontend/pweb/lib/pages/verification/page.dart index c0e901fe..5558d196 100644 --- a/frontend/pweb/lib/pages/verification/page.dart +++ b/frontend/pweb/lib/pages/verification/page.dart @@ -2,81 +2,26 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:pshared/provider/account.dart'; import 'package:pshared/provider/email_verification.dart'; -import 'package:pshared/widgets/locale.dart'; -import 'package:pweb/app/router/pages.dart'; -import 'package:pweb/pages/errors/error.dart'; -import 'package:pweb/pages/status/success.dart'; -import 'package:pweb/pages/with_footer.dart'; - -import 'package:pweb/generated/i18n/app_localizations.dart'; +import 'package:pweb/pages/verification/content.dart'; +import 'package:pweb/pages/verification/controller.dart'; -class AccountVerificationPage extends StatefulWidget { +class AccountVerificationPage extends StatelessWidget { final String token; const AccountVerificationPage({super.key, required this.token}); - @override - State createState() => _AccountVerificationPageState(); -} - -class _AccountVerificationPageState extends State { - @override - void initState() { - super.initState(); - context.read().verify(widget.token); - } - @override Widget build(BuildContext context) { - return const _AccountVerificationContent(); - } -} - -class _AccountVerificationContent extends StatelessWidget { - const _AccountVerificationContent(); - - @override - Widget build(BuildContext context) { - final locs = AppLocalizations.of(context)!; - final provider = context.watch(); - final action = OutlinedButton.icon( - onPressed: () => navigateAndReplace(context, Pages.login), - icon: const Icon(Icons.login), - label: Text(locs.login), - ); - - Widget content; - if (provider.isLoading) { - content = const Center(child: CircularProgressIndicator()); - } else if (provider.isSuccess) { - content = StatusPageSuccess( - successMessage: locs.accountVerified, - successDescription: locs.accountVerifiedDescription, - action: action, - ); - } else { - content = exceptionToErrorPage( - context: context, - title: locs.verificationFailed, - errorMessage: locs.accountVerificationFailed, - exception: provider.error ?? Exception(locs.accountVerificationFailed), - ); - } - - return PageWithFooter( - appBar: AppBar( - title: Text(locs.verifyAccount), - centerTitle: true, - actions: [ - const LocaleChangerDropdown( - availableLocales: AppLocalizations.supportedLocales, - ), - ], - ), - child: content, + return ChangeNotifierProvider( + create: (context) => AccountVerificationController( + accountProvider: context.read(), + verificationProvider: context.read(), + )..startVerification(token), + child: AccountVerificationContent(), ); } -} +} \ No newline at end of file diff --git a/frontend/pweb/lib/pages/verification/resend_dialog.dart b/frontend/pweb/lib/pages/verification/resend_dialog.dart new file mode 100644 index 00000000..810b09a2 --- /dev/null +++ b/frontend/pweb/lib/pages/verification/resend_dialog.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +import 'package:pweb/generated/i18n/app_localizations.dart'; + + +Future requestVerificationEmail( + BuildContext context, + AppLocalizations locs, +) async { + final controller = TextEditingController(); + final email = await showDialog( + context: context, + builder: (dialogContext) => AlertDialog( + title: Text(locs.signupConfirmationResend), + content: TextField( + controller: controller, + autofocus: true, + keyboardType: TextInputType.emailAddress, + decoration: InputDecoration( + labelText: locs.username, + hintText: locs.usernameHint, + ), + onSubmitted: (_) => + Navigator.of(dialogContext).pop(controller.text.trim()), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(dialogContext).pop(), + child: Text(locs.cancel), + ), + FilledButton( + onPressed: () => + Navigator.of(dialogContext).pop(controller.text.trim()), + child: Text(locs.signupConfirmationResend), + ), + ], + ), + ); + controller.dispose(); + return email?.trim(); +} -- 2.49.1 From e4fb270390d2242e83048b619754160d58d22c55 Mon Sep 17 00:00:00 2001 From: Arseni Date: Thu, 5 Feb 2026 12:00:05 +0300 Subject: [PATCH 3/5] fix for signup router --- frontend/pweb/lib/l10n/en.arb | 2 +- frontend/pweb/lib/l10n/ru.arb | 2 +- frontend/pweb/lib/pages/login/form.dart | 2 +- frontend/pweb/lib/pages/signup/form/content.dart | 4 ++-- frontend/pweb/lib/pages/signup/form/state.dart | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/pweb/lib/l10n/en.arb b/frontend/pweb/lib/l10n/en.arb index f8bff51f..786e9853 100644 --- a/frontend/pweb/lib/l10n/en.arb +++ b/frontend/pweb/lib/l10n/en.arb @@ -421,7 +421,7 @@ "paymentType": "Payment Method Type", "selectPaymentType": "Please select a payment method type", - "paymentTypeCard": "Credit Card", + "paymentTypeCard": "Russian bank card", "paymentTypeBankAccount": "Russian Bank Account", "paymentTypeIban": "IBAN", "paymentTypeWallet": "Wallet", diff --git a/frontend/pweb/lib/l10n/ru.arb b/frontend/pweb/lib/l10n/ru.arb index 28578227..8c2a92dc 100644 --- a/frontend/pweb/lib/l10n/ru.arb +++ b/frontend/pweb/lib/l10n/ru.arb @@ -421,7 +421,7 @@ "paymentType": "Тип способа оплаты", "selectPaymentType": "Пожалуйста, выберите тип способа оплаты", - "paymentTypeCard": "Кредитная карта", + "paymentTypeCard": "Российская банковская карта", "paymentTypeBankAccount": "Российский банковский счет", "paymentTypeIban": "IBAN", "paymentTypeWallet": "Кошелек", diff --git a/frontend/pweb/lib/pages/login/form.dart b/frontend/pweb/lib/pages/login/form.dart index 28224fb9..c2d1c565 100644 --- a/frontend/pweb/lib/pages/login/form.dart +++ b/frontend/pweb/lib/pages/login/form.dart @@ -109,7 +109,7 @@ class _LoginFormState extends State { builder: (context, isUsernameValid, child) => ValueListenableBuilder( valueListenable: _isPasswordAcceptable, builder: (context, isPasswordValid, child) => ButtonsRow( - onSignUp: () => navigate(context, Pages.signup), + onSignUp: () => navigateAndReplace(context, Pages.signup), login: () => _login( context, () => navigateAndReplace(context, Pages.dashboard), diff --git a/frontend/pweb/lib/pages/signup/form/content.dart b/frontend/pweb/lib/pages/signup/form/content.dart index 93cce12a..7767adbf 100644 --- a/frontend/pweb/lib/pages/signup/form/content.dart +++ b/frontend/pweb/lib/pages/signup/form/content.dart @@ -34,7 +34,7 @@ class SignUpFormContent extends StatelessWidget { Row( children: [ IconButton( - onPressed: Navigator.of(context).pop, + onPressed: onLogin, icon: Icon(Icons.arrow_back), ), ], @@ -59,4 +59,4 @@ class SignUpFormContent extends StatelessWidget { ), ), ); -} \ No newline at end of file +} diff --git a/frontend/pweb/lib/pages/signup/form/state.dart b/frontend/pweb/lib/pages/signup/form/state.dart index 91b93247..3914cb44 100644 --- a/frontend/pweb/lib/pages/signup/form/state.dart +++ b/frontend/pweb/lib/pages/signup/form/state.dart @@ -105,7 +105,7 @@ class SignUpFormState extends State { ), ); - void handleLogin() => navigate(context, Pages.login); + void handleLogin() => navigateAndReplace(context, Pages.login); @override void dispose() { -- 2.49.1 From 69ed9f25cb55f7dddd87a8a89f8682f40cfb0ca0 Mon Sep 17 00:00:00 2001 From: Arseni Date: Thu, 5 Feb 2026 12:02:48 +0300 Subject: [PATCH 4/5] deleted single payout that we will not use --- .../payouts/single/new_recipient/payout.dart | 57 ---------------- .../payouts/single/new_recipient/type.dart | 65 ------------------- .../dashboard/payouts/single/widget.dart | 8 +-- 3 files changed, 1 insertion(+), 129 deletions(-) delete mode 100644 frontend/pweb/lib/pages/dashboard/payouts/single/new_recipient/payout.dart delete mode 100644 frontend/pweb/lib/pages/dashboard/payouts/single/new_recipient/type.dart diff --git a/frontend/pweb/lib/pages/dashboard/payouts/single/new_recipient/payout.dart b/frontend/pweb/lib/pages/dashboard/payouts/single/new_recipient/payout.dart deleted file mode 100644 index cdad3294..00000000 --- a/frontend/pweb/lib/pages/dashboard/payouts/single/new_recipient/payout.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:pshared/models/payment/type.dart'; - -import 'package:pweb/models/control_state.dart'; -import 'package:pweb/pages/dashboard/payouts/single/new_recipient/type.dart'; -import 'package:pweb/utils/payment/availability.dart'; - - -class SinglePayout extends StatelessWidget { - final void Function(PaymentType type) onGoToPayment; - - static const double _cardPadding = 30.0; - static const double _dividerPaddingVertical = 12.0; - static const double _cardBorderRadius = 12.0; - static const double _dividerThickness = 1.0; - - const SinglePayout({super.key, required this.onGoToPayment}); - - @override - Widget build(BuildContext context) { - final paymentTypes = visiblePaymentTypes; - final dividerColor = Theme.of(context).dividerColor; - - return SizedBox( - width: double.infinity, - child: Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(_cardBorderRadius), - ), - elevation: 4, - color: Theme.of(context).colorScheme.onSecondary, - child: Padding( - padding: const EdgeInsets.all(_cardPadding), - child: Column( - children: [ - for (int i = 0; i < paymentTypes.length; i++) ...[ - PaymentTypeTile( - type: paymentTypes[i], - onSelected: onGoToPayment, - state: disabledPaymentTypes.contains(paymentTypes[i]) - ? ControlState.disabled - : ControlState.enabled, - ), - if (i < paymentTypes.length - 1) - Padding( - padding: const EdgeInsets.symmetric(vertical: _dividerPaddingVertical), - child: Divider(thickness: _dividerThickness, color: dividerColor), - ), - ], - ], - ), - ), - ), - ); - } -} diff --git a/frontend/pweb/lib/pages/dashboard/payouts/single/new_recipient/type.dart b/frontend/pweb/lib/pages/dashboard/payouts/single/new_recipient/type.dart deleted file mode 100644 index c191fda1..00000000 --- a/frontend/pweb/lib/pages/dashboard/payouts/single/new_recipient/type.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:pshared/models/payment/type.dart'; - -import 'package:pweb/models/control_state.dart'; -import 'package:pweb/pages/payment_methods/icon.dart'; -import 'package:pweb/utils/payment/label.dart'; - - -class PaymentTypeTile extends StatelessWidget { - final PaymentType type; - final void Function(PaymentType type) onSelected; - final ControlState state; - - const PaymentTypeTile({ - super.key, - required this.type, - required this.onSelected, - this.state = ControlState.enabled, - }); - - @override - Widget build(BuildContext context) { - final label = getPaymentTypeLabel(context, type); - - final theme = Theme.of(context); - final isEnabled = state == ControlState.enabled; - final isDisabled = state == ControlState.disabled; - final isLoading = state == ControlState.loading; - final textColor = isDisabled - ? theme.colorScheme.onSurface.withValues(alpha: 0.55) - : theme.colorScheme.onSurface; - - return InkWell( - borderRadius: BorderRadius.circular(8), - onTap: isEnabled ? () => onSelected(type) : null, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row( - children: [ - Icon(iconForPaymentType(type), size: 24, color: textColor), - const SizedBox(width: 12), - Text( - label, - style: theme.textTheme.bodyMedium?.copyWith(color: textColor), - ), - if (isLoading) ...[ - const SizedBox(width: 12), - SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - theme.colorScheme.primary, - ), - ), - ), - ], - ], - ), - ), - ); - } -} diff --git a/frontend/pweb/lib/pages/dashboard/payouts/single/widget.dart b/frontend/pweb/lib/pages/dashboard/payouts/single/widget.dart index 8336d558..705127f5 100644 --- a/frontend/pweb/lib/pages/dashboard/payouts/single/widget.dart +++ b/frontend/pweb/lib/pages/dashboard/payouts/single/widget.dart @@ -4,7 +4,6 @@ import 'package:pshared/models/payment/type.dart'; import 'package:pshared/models/recipient/recipient.dart'; import 'package:pweb/pages/dashboard/payouts/single/address_book/widget.dart'; -import 'package:pweb/pages/dashboard/payouts/single/new_recipient/payout.dart'; class SinglePayoutForm extends StatelessWidget { @@ -17,18 +16,13 @@ class SinglePayoutForm extends StatelessWidget { required this.onGoToPayment, }); - static const double _spacingBetweenAddressAndForm = 20.0; - static const double _bottomSpacing = 40.0; - @override Widget build(BuildContext context) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ AddressBookPayout(onSelected: onRecipientSelected), - const SizedBox(height: _spacingBetweenAddressAndForm), - SinglePayout(onGoToPayment: onGoToPayment), - const SizedBox(height: _bottomSpacing), + //TODO Add history of recent wallets/payments ], ); } -- 2.49.1 From b27eed31b7396be7c366b84fc15fea5bcd68e55e Mon Sep 17 00:00:00 2001 From: Arseni Date: Thu, 5 Feb 2026 13:04:00 +0300 Subject: [PATCH 5/5] removed status from recipient address book --- .../address_book/page/filter_button.dart | 61 ------------------- .../lib/pages/address_book/page/page.dart | 38 ------------ .../address_book/page/recipient/item.dart | 5 -- .../address_book/page/recipient/status.dart | 32 ---------- 4 files changed, 136 deletions(-) delete mode 100644 frontend/pweb/lib/pages/address_book/page/filter_button.dart delete mode 100644 frontend/pweb/lib/pages/address_book/page/recipient/status.dart diff --git a/frontend/pweb/lib/pages/address_book/page/filter_button.dart b/frontend/pweb/lib/pages/address_book/page/filter_button.dart deleted file mode 100644 index b4f1a801..00000000 --- a/frontend/pweb/lib/pages/address_book/page/filter_button.dart +++ /dev/null @@ -1,61 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:pshared/models/recipient/filter.dart'; - - -class RecipientFilterButton extends StatelessWidget { - final String text; - final RecipientFilter filter; - final RecipientFilter selected; - final ValueChanged onTap; - - const RecipientFilterButton({ - super.key, - required this.text, - required this.filter, - required this.selected, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - final isSelected = selected == filter; - final theme = Theme.of(context).colorScheme; - - return ElevatedButton( - onPressed: () => onTap(filter), - style: ButtonStyle( - backgroundColor: WidgetStateProperty.all(Colors.transparent), - overlayColor: WidgetStateProperty.all(Colors.transparent), - shadowColor: WidgetStateProperty.all(Colors.transparent), - elevation: WidgetStateProperty.all(0), - ), - child: IntrinsicWidth( - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - text, - style: TextStyle( - fontSize: 20, - color: isSelected - ? theme.onPrimaryContainer - : theme.onPrimaryContainer.withAlpha(60), - ), - ), - SizedBox( - height: 2, - child: DecoratedBox( - decoration: BoxDecoration( - color: isSelected - ? theme.primary - : theme.onPrimaryContainer.withAlpha(60), - ), - ), - ), - ], - ), - ), - ); - } -} diff --git a/frontend/pweb/lib/pages/address_book/page/page.dart b/frontend/pweb/lib/pages/address_book/page/page.dart index 85c56e4e..b2afacb8 100644 --- a/frontend/pweb/lib/pages/address_book/page/page.dart +++ b/frontend/pweb/lib/pages/address_book/page/page.dart @@ -3,11 +3,9 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'package:pshared/models/recipient/recipient.dart'; -import 'package:pshared/models/recipient/filter.dart'; import 'package:pshared/provider/recipient/provider.dart'; import 'package:pweb/pages/address_book/page/empty.dart'; -import 'package:pweb/pages/address_book/page/filter_button.dart'; import 'package:pweb/pages/address_book/page/header.dart'; import 'package:pweb/pages/address_book/page/list.dart'; import 'package:pweb/pages/address_book/page/search.dart'; @@ -42,7 +40,6 @@ class RecipientAddressBookPage extends StatefulWidget { class _RecipientAddressBookPageState extends State { late final TextEditingController _searchController; late final FocusNode _searchFocusNode; - RecipientFilter _selectedFilter = RecipientFilter.all; String _query = ''; @override @@ -65,19 +62,12 @@ class _RecipientAddressBookPageState extends State { }); } - void _setFilter(RecipientFilter filter) { - setState(() { - _selectedFilter = filter; - }); - } - @override Widget build(BuildContext context) { final loc = AppLocalizations.of(context)!; final provider = context.watch(); final filteredRecipients = filterRecipients( recipients: provider.recipients, - filter: _selectedFilter, query: _query, ); @@ -100,34 +90,6 @@ class _RecipientAddressBookPageState extends State { onChanged: _setQuery, ), const SizedBox(height: RecipientAddressBookPage._bigBox), - Row( - children: [ - RecipientFilterButton( - text: loc.allStatus, - filter: RecipientFilter.all, - selected: _selectedFilter, - onTap: _setFilter, - ), - RecipientFilterButton( - text: loc.readyStatus, - filter: RecipientFilter.ready, - selected: _selectedFilter, - onTap: _setFilter, - ), - RecipientFilterButton( - text: loc.registeredStatus, - filter: RecipientFilter.registered, - selected: _selectedFilter, - onTap: _setFilter, - ), - RecipientFilterButton( - text: loc.notRegisteredStatus, - filter: RecipientFilter.notRegistered, - selected: _selectedFilter, - onTap: _setFilter, - ), - ], - ), SizedBox( height: RecipientAddressBookPage._expandedHeight, child: Padding( diff --git a/frontend/pweb/lib/pages/address_book/page/recipient/item.dart b/frontend/pweb/lib/pages/address_book/page/recipient/item.dart index af5d7bbc..9df753c1 100644 --- a/frontend/pweb/lib/pages/address_book/page/recipient/item.dart +++ b/frontend/pweb/lib/pages/address_book/page/recipient/item.dart @@ -5,7 +5,6 @@ import 'package:pshared/models/recipient/recipient.dart'; import 'package:pweb/pages/address_book/page/recipient/actions.dart'; import 'package:pweb/pages/address_book/page/recipient/info_column.dart'; import 'package:pweb/pages/address_book/page/recipient/payment_row.dart'; -import 'package:pweb/pages/address_book/page/recipient/status.dart'; import 'package:pweb/pages/dashboard/payouts/single/address_book/avatar.dart'; @@ -18,7 +17,6 @@ class RecipientAddressBookItem extends StatefulWidget { final double borderRadius; final double elevation; final EdgeInsetsGeometry padding; - final double spacingDotAvatar; final double spacingAvatarInfo; final double spacingBottom; final double avatarRadius; @@ -32,7 +30,6 @@ class RecipientAddressBookItem extends StatefulWidget { this.borderRadius = 12, this.elevation = 4, this.padding = const EdgeInsets.symmetric(horizontal: 16, vertical: 8), - this.spacingDotAvatar = 8, this.spacingAvatarInfo = 16, this.spacingBottom = 10, this.avatarRadius = 24, @@ -65,8 +62,6 @@ class _RecipientAddressBookItemState extends State { children: [ Row( children: [ - RecipientStatusDot(status: recipient.status), - SizedBox(width: widget.spacingDotAvatar), RecipientAvatar( name: recipient.name, avatarUrl: recipient.avatarUrl, diff --git a/frontend/pweb/lib/pages/address_book/page/recipient/status.dart b/frontend/pweb/lib/pages/address_book/page/recipient/status.dart deleted file mode 100644 index a9a7db39..00000000 --- a/frontend/pweb/lib/pages/address_book/page/recipient/status.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:pshared/models/recipient/status.dart'; - - -class RecipientStatusDot extends StatelessWidget { - final RecipientStatus status; - - const RecipientStatusDot({super.key, required this.status}); - - @override - Widget build(BuildContext context) { - Color color; - switch (status) { - case RecipientStatus.ready: - color = Colors.green; - break; - case RecipientStatus.notRegistered: - color = Theme.of(context).colorScheme.error; - break; - case RecipientStatus.registered: - color = Colors.yellow; - break; - } - - return Container( - width: 12, - height: 12, - decoration: BoxDecoration(shape: BoxShape.circle, color: color), - ); - } -} -- 2.49.1