conflicts resolution
This commit is contained in:
BIN
frontend/.DS_Store
vendored
Normal file
BIN
frontend/.DS_Store
vendored
Normal file
Binary file not shown.
BIN
frontend/pshared/.DS_Store
vendored
Normal file
BIN
frontend/pshared/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -22,10 +22,6 @@ enum ResourceType {
|
|||||||
@JsonValue('clients')
|
@JsonValue('clients')
|
||||||
clients,
|
clients,
|
||||||
|
|
||||||
/// Represents comments on tasks or other resources
|
|
||||||
@JsonValue('comments')
|
|
||||||
comments,
|
|
||||||
|
|
||||||
/// Represents invitations sent to users
|
/// Represents invitations sent to users
|
||||||
@JsonValue('invitations')
|
@JsonValue('invitations')
|
||||||
invitations,
|
invitations,
|
||||||
@@ -46,6 +42,9 @@ enum ResourceType {
|
|||||||
@JsonValue('organizations')
|
@JsonValue('organizations')
|
||||||
organizations,
|
organizations,
|
||||||
|
|
||||||
|
@JsonValue('chain_wallets')
|
||||||
|
chainWallets,
|
||||||
|
|
||||||
/// Represents permissions service
|
/// Represents permissions service
|
||||||
@JsonValue('permissions')
|
@JsonValue('permissions')
|
||||||
permissions,
|
permissions,
|
||||||
@@ -54,25 +53,6 @@ enum ResourceType {
|
|||||||
@JsonValue('policies')
|
@JsonValue('policies')
|
||||||
policies,
|
policies,
|
||||||
|
|
||||||
/// Represents task or project priorities
|
|
||||||
@JsonValue('priorities')
|
|
||||||
priorities,
|
|
||||||
|
|
||||||
/// Represents priority groups
|
|
||||||
@JsonValue('priority_groups')
|
|
||||||
priorityGroups,
|
|
||||||
|
|
||||||
/// Represents projects managed in the system
|
|
||||||
@JsonValue('projects')
|
|
||||||
projects,
|
|
||||||
|
|
||||||
@JsonValue('properties')
|
|
||||||
properties,
|
|
||||||
|
|
||||||
/// Represents reactions
|
|
||||||
@JsonValue('reactions')
|
|
||||||
reactions,
|
|
||||||
|
|
||||||
/// Represents refresh tokens for authentication
|
/// Represents refresh tokens for authentication
|
||||||
@JsonValue('refresh_tokens')
|
@JsonValue('refresh_tokens')
|
||||||
refreshTokens,
|
refreshTokens,
|
||||||
@@ -88,20 +68,4 @@ enum ResourceType {
|
|||||||
/// Represents steps in workflows or processes
|
/// Represents steps in workflows or processes
|
||||||
@JsonValue('steps')
|
@JsonValue('steps')
|
||||||
steps,
|
steps,
|
||||||
|
|
||||||
/// Represents tasks managed in the system
|
|
||||||
@JsonValue('tasks')
|
|
||||||
tasks,
|
|
||||||
|
|
||||||
/// Represents teams managed in the system
|
|
||||||
@JsonValue('teams')
|
|
||||||
teams,
|
|
||||||
|
|
||||||
/// Represents workflows for tasks or projects
|
|
||||||
@JsonValue('workflows')
|
|
||||||
workflows,
|
|
||||||
|
|
||||||
/// Represents workspaces containing projects and teams
|
|
||||||
@JsonValue('workspaces')
|
|
||||||
workspaces;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,8 +21,8 @@ class ResourceContainer<T extends GenericProvider> extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Consumer<T>(builder: (context, provider, _) {
|
Widget build(BuildContext context) => Consumer<T>(builder: (context, provider, _) {
|
||||||
if (provider.isLoading) return loading ?? Center(child: CircularProgressIndicator());
|
if (provider.isLoading) return loading ?? Center(child: CircularProgressIndicator());
|
||||||
if (provider.error != null) return error ?? Text('Error while loading data. Try again'); //TODO: need to implement localizations and add more details to the error
|
if (provider.error != null) return error ?? Text('Error while loading data. Try again');
|
||||||
if (provider.isEmpty) return empty ?? Text('Empty data'); //TODO: need to implement localizations too
|
if (provider.isEmpty) return empty ?? Text('Empty data');
|
||||||
return builder(context, provider);
|
return builder(context, provider);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
3
frontend/pweb/devtools_options.yaml
Normal file
3
frontend/pweb/devtools_options.yaml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
description: This file stores settings for Dart & Flutter DevTools.
|
||||||
|
documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states
|
||||||
|
extensions:
|
||||||
@@ -5,6 +5,6 @@ class Constants {
|
|||||||
class AppConfig {
|
class AppConfig {
|
||||||
static const String appName = String.fromEnvironment(
|
static const String appName = String.fromEnvironment(
|
||||||
'APP_NAME',
|
'APP_NAME',
|
||||||
defaultValue: 'SendiCo',
|
defaultValue: 'sendico',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,9 +36,9 @@
|
|||||||
"signupError": "Failed to signup: {error}",
|
"signupError": "Failed to signup: {error}",
|
||||||
"signupSuccess": "Email confirmation message has been sent to {email}. Please, open it and click link to activate your account.",
|
"signupSuccess": "Email confirmation message has been sent to {email}. Please, open it and click link to activate your account.",
|
||||||
"connectivityError": "Cannot reach the server at {serverAddress}. Check your network and try again.",
|
"connectivityError": "Cannot reach the server at {serverAddress}. Check your network and try again.",
|
||||||
"errorAccountExists": "Account already exists",
|
|
||||||
"errorAccountNotVerified": "Your account hasn't been verified yet. Please check your email to complete the verification",
|
"errorAccountNotVerified": "Your account hasn't been verified yet. Please check your email to complete the verification",
|
||||||
"errorLoginUnauthorized": "Login or password is incorrect. Please try again",
|
"errorLoginUnauthorized": "Login or password is incorrect. Please try again",
|
||||||
|
"errorAccountExists": "Account with this login already exists",
|
||||||
"errorInternalError": "An internal error occurred. We're aware of the issue and working to resolve it. Please try again later",
|
"errorInternalError": "An internal error occurred. We're aware of the issue and working to resolve it. Please try again later",
|
||||||
"errorVerificationTokenNotFound": "Account for verification not found. Sign up again",
|
"errorVerificationTokenNotFound": "Account for verification not found. Sign up again",
|
||||||
"created": "Created",
|
"created": "Created",
|
||||||
@@ -441,5 +441,36 @@
|
|||||||
"verificationStatusErrorUnknown": "Unexpected error occurred while verification. Try once again or contact support",
|
"verificationStatusErrorUnknown": "Unexpected error occurred while verification. Try once again or contact support",
|
||||||
"accountVerified": "Account Verified!",
|
"accountVerified": "Account Verified!",
|
||||||
"accountVerifiedDescription": "Your account has been successfully verified. You can now log in to access your account.",
|
"accountVerifiedDescription": "Your account has been successfully verified. You can now log in to access your account.",
|
||||||
"retryVerification": "Retry Verification"
|
"retryVerification": "Retry Verification",
|
||||||
|
"save": "Save",
|
||||||
|
"editWallet": "Edit Wallet",
|
||||||
|
"userNamePlaceholder": "User Name",
|
||||||
|
"noWalletSelected": "No wallet selected",
|
||||||
|
"noWalletsAvailable": "No wallets available",
|
||||||
|
"walletActivity": "Wallet activity",
|
||||||
|
"reset": "Reset",
|
||||||
|
"failedToLoadHistory": "Failed to load history",
|
||||||
|
"retry": "Retry",
|
||||||
|
"walletName": "Wallet name",
|
||||||
|
"walletNameSaved": "Wallet name saved",
|
||||||
|
"topUpBalance": "Top Up Balance",
|
||||||
|
"addFunctionality": "Add functionality",
|
||||||
|
"walletHistoryEmpty": "No history yet",
|
||||||
|
"colType": "Type",
|
||||||
|
"colAmount": "Amount",
|
||||||
|
"colBalance": "Balance",
|
||||||
|
"colCounterparty": "Counterparty",
|
||||||
|
"colDate": "Date",
|
||||||
|
"colComment": "Comment",
|
||||||
|
"recipientNoPaymentDetails": "This recipient has no available payment details.",
|
||||||
|
"paymentInfo": "Payment info",
|
||||||
|
"recipient": "Recipient",
|
||||||
|
"chooseAnotherRecipient": "Choose another recipient",
|
||||||
|
"noRecipientsYet": "No recipients yet.",
|
||||||
|
"noRecipientsFound": "No recipients found for this query.",
|
||||||
|
"sourceOfFunds": "Source of funds",
|
||||||
|
"walletTopUp": "Top up",
|
||||||
|
"englishLanguage": "English",
|
||||||
|
"russianLanguage": "Russian",
|
||||||
|
"germanLanguage": "German"
|
||||||
}
|
}
|
||||||
@@ -38,6 +38,7 @@
|
|||||||
"connectivityError": "Не удается связаться с сервером {serverAddress}. Проверьте ваше интернет-соединение и попробуйте снова.",
|
"connectivityError": "Не удается связаться с сервером {serverAddress}. Проверьте ваше интернет-соединение и попробуйте снова.",
|
||||||
"errorAccountNotVerified": "Ваш аккаунт еще не подтвержден. Пожалуйста, проверьте вашу электронную почту для завершения верификации",
|
"errorAccountNotVerified": "Ваш аккаунт еще не подтвержден. Пожалуйста, проверьте вашу электронную почту для завершения верификации",
|
||||||
"errorLoginUnauthorized": "Неверный логин или пароль. Пожалуйста, попробуйте снова",
|
"errorLoginUnauthorized": "Неверный логин или пароль. Пожалуйста, попробуйте снова",
|
||||||
|
"errorAccountExists": "Аккаунт с таким логином уже существует",
|
||||||
"errorInternalError": "Произошла внутренняя ошибка. Мы в курсе проблемы и работаем над ее решением. Пожалуйста, попробуйте позже",
|
"errorInternalError": "Произошла внутренняя ошибка. Мы в курсе проблемы и работаем над ее решением. Пожалуйста, попробуйте позже",
|
||||||
"errorVerificationTokenNotFound": "Аккаунт для верификации не найден. Зарегистрируйтесь снова",
|
"errorVerificationTokenNotFound": "Аккаунт для верификации не найден. Зарегистрируйтесь снова",
|
||||||
"created": "Создано",
|
"created": "Создано",
|
||||||
@@ -433,5 +434,36 @@
|
|||||||
"verificationStatusErrorUnknown": "Произошла непредвиденная ошибка при подтверждении. Попробуйте еще раз или обратитесь в службу поддержки",
|
"verificationStatusErrorUnknown": "Произошла непредвиденная ошибка при подтверждении. Попробуйте еще раз или обратитесь в службу поддержки",
|
||||||
"accountVerified": "Аккаунт подтвержден!",
|
"accountVerified": "Аккаунт подтвержден!",
|
||||||
"accountVerifiedDescription": "Ваш аккаунт успешно подтвержден. Теперь вы можете войти, чтобы получить доступ к своему аккаунту",
|
"accountVerifiedDescription": "Ваш аккаунт успешно подтвержден. Теперь вы можете войти, чтобы получить доступ к своему аккаунту",
|
||||||
"retryVerification": "Повторить подтверждение"
|
"retryVerification": "Повторить подтверждение",
|
||||||
|
"save": "Сохранить",
|
||||||
|
"editWallet": "Редактировать кошелек",
|
||||||
|
"userNamePlaceholder": "Имя пользователя",
|
||||||
|
"noWalletSelected": "Кошелек не выбран",
|
||||||
|
"noWalletsAvailable": "Кошельки отсутствуют",
|
||||||
|
"walletActivity": "Активность кошелька",
|
||||||
|
"reset": "Сбросить",
|
||||||
|
"failedToLoadHistory": "Не удалось загрузить историю",
|
||||||
|
"retry": "Повторить",
|
||||||
|
"walletName": "Название кошелька",
|
||||||
|
"walletNameSaved": "Название кошелька сохранено",
|
||||||
|
"topUpBalance": "Пополнить баланс",
|
||||||
|
"addFunctionality": "Добавить функциональность",
|
||||||
|
"walletHistoryEmpty": "История пуста",
|
||||||
|
"colType": "Тип",
|
||||||
|
"colAmount": "Сумма",
|
||||||
|
"colBalance": "Баланс",
|
||||||
|
"colCounterparty": "Контрагент",
|
||||||
|
"colDate": "Дата",
|
||||||
|
"colComment": "Комментарий",
|
||||||
|
"recipientNoPaymentDetails": "У этого получателя нет доступных платежных данных.",
|
||||||
|
"paymentInfo": "Платежная информация",
|
||||||
|
"recipient": "Получатель",
|
||||||
|
"chooseAnotherRecipient": "Выбрать другого получателя",
|
||||||
|
"noRecipientsYet": "Получателей пока нет.",
|
||||||
|
"noRecipientsFound": "Получатели по запросу не найдены.",
|
||||||
|
"sourceOfFunds": "Источник средств",
|
||||||
|
"walletTopUp": "Пополнение",
|
||||||
|
"englishLanguage": "Английский",
|
||||||
|
"russianLanguage": "Русский",
|
||||||
|
"germanLanguage": "Немецкий"
|
||||||
}
|
}
|
||||||
@@ -8,8 +8,9 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
import 'package:pshared/config/constants.dart';
|
import 'package:pshared/config/constants.dart';
|
||||||
import 'package:pshared/provider/account.dart';
|
|
||||||
import 'package:pshared/provider/locale.dart';
|
import 'package:pshared/provider/locale.dart';
|
||||||
|
import 'package:pshared/provider/permissions.dart';
|
||||||
|
import 'package:pshared/provider/account.dart';
|
||||||
import 'package:pshared/provider/organizations.dart';
|
import 'package:pshared/provider/organizations.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/app.dart';
|
import 'package:pweb/app/app.dart';
|
||||||
@@ -63,6 +64,11 @@ void main() async {
|
|||||||
create: (_) => TwoFactorProvider(),
|
create: (_) => TwoFactorProvider(),
|
||||||
update: (context, accountProvider, provider) => provider!..update(accountProvider),
|
update: (context, accountProvider, provider) => provider!..update(accountProvider),
|
||||||
),
|
),
|
||||||
|
ChangeNotifierProxyProvider<OrganizationsProvider, PermissionsProvider>(
|
||||||
|
create: (_) => PermissionsProvider(),
|
||||||
|
update: (context, orgnization, provider) => provider!..update(orgnization),
|
||||||
|
),
|
||||||
|
ChangeNotifierProvider(create: (_) => TwoFactorProvider()),
|
||||||
ChangeNotifierProvider(create: (_) => OrganizationsProvider()),
|
ChangeNotifierProvider(create: (_) => OrganizationsProvider()),
|
||||||
ChangeNotifierProvider(create: (_) => CarouselIndexProvider()),
|
ChangeNotifierProvider(create: (_) => CarouselIndexProvider()),
|
||||||
|
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/payment/status.dart';
|
import 'package:pshared/models/payment/status.dart';
|
||||||
|
|
||||||
import 'package:pweb/models/currency.dart';
|
import 'package:pweb/models/currency.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
enum WalletTransactionType { topUp, payout }
|
enum WalletTransactionType { topUp, payout }
|
||||||
|
|
||||||
extension WalletTransactionTypeX on WalletTransactionType {
|
extension WalletTransactionTypeX on WalletTransactionType {
|
||||||
String label(BuildContext context) {
|
String label(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
switch (this) {
|
switch (this) {
|
||||||
case WalletTransactionType.topUp:
|
case WalletTransactionType.topUp:
|
||||||
return 'Top up';
|
return loc.walletTopUp;
|
||||||
case WalletTransactionType.payout:
|
case WalletTransactionType.payout:
|
||||||
return 'Payout';
|
return loc.payout;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/recipient/recipient.dart';
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/recipient/filter.dart';
|
import 'package:pshared/models/recipient/filter.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/address_book/page/filter_button.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/header.dart';
|
||||||
import 'package:pweb/pages/address_book/page/list.dart';
|
import 'package:pweb/pages/address_book/page/list.dart';
|
||||||
@@ -14,7 +14,7 @@ import 'package:pweb/providers/recipient.dart';
|
|||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class RecipientAddressBookPage extends StatelessWidget {
|
class RecipientAddressBookPage extends StatefulWidget {
|
||||||
final ValueChanged<Recipient> onRecipientSelected;
|
final ValueChanged<Recipient> onRecipientSelected;
|
||||||
final VoidCallback onAddRecipient;
|
final VoidCallback onAddRecipient;
|
||||||
final ValueChanged<Recipient>? onEditRecipient;
|
final ValueChanged<Recipient>? onEditRecipient;
|
||||||
@@ -31,32 +31,65 @@ class RecipientAddressBookPage extends StatelessWidget {
|
|||||||
static const double _bigBox = 30;
|
static const double _bigBox = 30;
|
||||||
static const double _smallBox = 20;
|
static const double _smallBox = 20;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RecipientAddressBookPage> createState() => _RecipientAddressBookPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RecipientAddressBookPageState extends State<RecipientAddressBookPage> {
|
||||||
|
late final TextEditingController _searchController;
|
||||||
|
late final FocusNode _searchFocusNode;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
final provider = context.read<RecipientProvider>();
|
||||||
|
_searchController = TextEditingController(text: provider.query);
|
||||||
|
_searchFocusNode = FocusNode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_searchController.dispose();
|
||||||
|
_searchFocusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _syncSearchField(RecipientProvider provider) {
|
||||||
|
final query = provider.query;
|
||||||
|
if (_searchController.text == query) return;
|
||||||
|
|
||||||
|
_searchController.value = TextEditingValue(
|
||||||
|
text: query,
|
||||||
|
selection: TextSelection.collapsed(offset: query.length),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|
||||||
final loc = AppLocalizations.of(context)!;
|
final loc = AppLocalizations.of(context)!;
|
||||||
final provider = context.watch<RecipientProvider>();
|
final provider = context.watch<RecipientProvider>();
|
||||||
|
_syncSearchField(provider);
|
||||||
|
|
||||||
if (provider.isLoading) {
|
if (provider.isLoading) {
|
||||||
return const Center(child: CircularProgressIndicator()); //TODO This should be in the provider
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider.error != null) {
|
if (provider.error != null) {
|
||||||
return Center(child: Text('Error: ${provider.error}'));
|
return Center(child: Text(loc.notificationError(provider.error ?? loc.noErrorInformation)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
RecipientAddressBookHeader(onAddRecipient: onAddRecipient),
|
RecipientAddressBookHeader(onAddRecipient: widget.onAddRecipient),
|
||||||
const SizedBox(height: _smallBox),
|
const SizedBox(height: RecipientAddressBookPage._smallBox),
|
||||||
RecipientSearchField(
|
RecipientSearchField(
|
||||||
controller: TextEditingController(text: provider.query),
|
controller: _searchController,
|
||||||
focusNode: FocusNode(),
|
focusNode: _searchFocusNode,
|
||||||
onChanged: provider.setQuery,
|
onChanged: provider.setQuery,
|
||||||
),
|
),
|
||||||
const SizedBox(height: _bigBox),
|
const SizedBox(height: RecipientAddressBookPage._bigBox),
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
RecipientFilterButton(
|
RecipientFilterButton(
|
||||||
@@ -86,13 +119,13 @@ class RecipientAddressBookPage extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: _expandedHeight,
|
height: RecipientAddressBookPage._expandedHeight,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(_paddingAll),
|
padding: const EdgeInsets.all(RecipientAddressBookPage._paddingAll),
|
||||||
child: RecipientAddressBookList(
|
child: RecipientAddressBookList(
|
||||||
filteredRecipients: provider.filteredRecipients,
|
filteredRecipients: provider.filteredRecipients,
|
||||||
onEdit: (recipient) => onEditRecipient?.call(recipient),
|
onEdit: (recipient) => widget.onEditRecipient?.call(recipient),
|
||||||
onSelected: onRecipientSelected,
|
onSelected: widget.onRecipientSelected,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class BalanceAddFunds extends StatelessWidget {
|
class BalanceAddFunds extends StatelessWidget {
|
||||||
final VoidCallback onTopUp;
|
final VoidCallback onTopUp;
|
||||||
@@ -19,6 +21,7 @@ class BalanceAddFunds extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final textTheme = Theme.of(context).textTheme;
|
final textTheme = Theme.of(context).textTheme;
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTopUp,
|
onTap: onTopUp,
|
||||||
@@ -37,7 +40,7 @@ class BalanceAddFunds extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: _spacingMedium),
|
const SizedBox(width: _spacingMedium),
|
||||||
Text(
|
Text(
|
||||||
'Add funds',
|
loc.addFunds,
|
||||||
style: textTheme.bodyMedium?.copyWith(
|
style: textTheme.bodyMedium?.copyWith(
|
||||||
color: colorScheme.primary,
|
color: colorScheme.primary,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:pweb/pages/dashboard/buttons/balance/carousel.dart';
|
import 'package:pweb/pages/dashboard/buttons/balance/carousel.dart';
|
||||||
import 'package:pweb/providers/wallets.dart';
|
import 'package:pweb/providers/wallets.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class BalanceWidget extends StatelessWidget {
|
class BalanceWidget extends StatelessWidget {
|
||||||
const BalanceWidget({super.key});
|
const BalanceWidget({super.key});
|
||||||
@@ -12,6 +14,7 @@ class BalanceWidget extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final walletsProvider = context.watch<WalletsProvider>();
|
final walletsProvider = context.watch<WalletsProvider>();
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
if (walletsProvider.isLoading) {
|
if (walletsProvider.isLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
@@ -20,7 +23,7 @@ class BalanceWidget extends StatelessWidget {
|
|||||||
final wallets = walletsProvider.wallets;
|
final wallets = walletsProvider.wallets;
|
||||||
|
|
||||||
if (wallets == null || wallets.isEmpty) {
|
if (wallets == null || wallets.isEmpty) {
|
||||||
return const Center(child: Text('No wallets available'));
|
return Center(child: Text(loc.noWalletsAvailable));
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ class UploadHistorySection extends StatelessWidget {
|
|||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
if (provider.error != null) {
|
if (provider.error != null) {
|
||||||
return Text("Error: ${provider.error}");
|
return Text(l10.notificationError(provider.error ?? l10.noErrorInformation));
|
||||||
}
|
}
|
||||||
final items = provider.data ?? [];
|
final items = provider.data ?? [];
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import 'package:pweb/pages/dashboard/payouts/single/adress_book/long_list/long_l
|
|||||||
import 'package:pweb/pages/dashboard/payouts/single/adress_book/short_list.dart';
|
import 'package:pweb/pages/dashboard/payouts/single/adress_book/short_list.dart';
|
||||||
import 'package:pweb/providers/recipient.dart';
|
import 'package:pweb/providers/recipient.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class AdressBookPayout extends StatefulWidget {
|
class AdressBookPayout extends StatefulWidget {
|
||||||
final ValueChanged<Recipient> onSelected;
|
final ValueChanged<Recipient> onSelected;
|
||||||
@@ -54,6 +56,7 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
final provider = context.watch<RecipientProvider>();
|
final provider = context.watch<RecipientProvider>();
|
||||||
|
|
||||||
if (provider.isLoading) {
|
if (provider.isLoading) {
|
||||||
@@ -61,7 +64,7 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (provider.error != null) {
|
if (provider.error != null) {
|
||||||
return Center(child: Text('Error: ${provider.error}'));
|
return Center(child: Text(loc.notificationError(provider.error ?? loc.noErrorInformation)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
|
|||||||
@@ -26,13 +26,10 @@ class AccountLoader extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
navigateAndReplace(context, Pages.login);
|
navigateAndReplace(context, Pages.login);
|
||||||
}
|
}
|
||||||
if ((provider.error == null) && (provider.account == null)) {
|
if (provider.account == null) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) => navigateAndReplace(context, Pages.login));
|
||||||
provider.restore();
|
|
||||||
});
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
return child;
|
return child;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/provider/account.dart';
|
||||||
import 'package:pshared/provider/permissions.dart';
|
import 'package:pshared/provider/permissions.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/pages.dart';
|
import 'package:pweb/app/router/pages.dart';
|
||||||
@@ -16,8 +17,10 @@ class PermissionsLoader extends StatelessWidget {
|
|||||||
const PermissionsLoader({super.key, required this.child});
|
const PermissionsLoader({super.key, required this.child});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Consumer<PermissionsProvider>(builder: (context, provider, _) {
|
Widget build(BuildContext context) => Consumer2<PermissionsProvider, AccountProvider>(builder: (context, provider, accountProvider, _) {
|
||||||
if (provider.isLoading) return const Center(child: CircularProgressIndicator());
|
if (provider.isLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
if (provider.error != null) {
|
if (provider.error != null) {
|
||||||
postNotifyUserOfErrorX(
|
postNotifyUserOfErrorX(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -26,7 +29,7 @@ class PermissionsLoader extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
navigateAndReplace(context, Pages.login);
|
navigateAndReplace(context, Pages.login);
|
||||||
}
|
}
|
||||||
if ((provider.error == null) && (provider.permissions.isEmpty)) {
|
if (provider.error == null && !provider.isReady && accountProvider.account != null) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
provider.load();
|
provider.load();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ class PaymentMethodTypeSelector extends StatelessWidget {
|
|||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
return DropdownButtonFormField<PaymentType>(
|
return DropdownButtonFormField<PaymentType>(
|
||||||
value: value,
|
initialValue: value,
|
||||||
decoration: InputDecoration(labelText: l10n.paymentType),
|
decoration: InputDecoration(labelText: l10n.paymentType),
|
||||||
items: PaymentType.values.map((type) {
|
items: PaymentType.values.map((type) {
|
||||||
final label = getPaymentTypeLabel(context, type);
|
final label = getPaymentTypeLabel(context, type);
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/payment_methods/icon.dart';
|
|
||||||
import 'package:pshared/models/payment/methods/type.dart';
|
import 'package:pshared/models/payment/methods/type.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/payment_methods/icon.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentMethodTile extends StatelessWidget {
|
class PaymentMethodTile extends StatelessWidget {
|
||||||
const PaymentMethodTile({
|
const PaymentMethodTile({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -62,7 +64,7 @@ class PaymentMethodTile extends StatelessWidget {
|
|||||||
Widget _buildMakeMainButton(BuildContext context) {
|
Widget _buildMakeMainButton(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
return IconButton(
|
return IconButton(
|
||||||
tooltip: 'Make main',
|
tooltip: AppLocalizations.of(context)!.makeMain,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
method.isMain ? Icons.star : Icons.star_outline,
|
method.isMain ? Icons.star : Icons.star_outline,
|
||||||
color: method.isMain ? theme.colorScheme.primary : null,
|
color: method.isMain ? theme.colorScheme.primary : null,
|
||||||
|
|||||||
67
frontend/pweb/lib/pages/payment_methods/widgets/card.dart
Normal file
67
frontend/pweb/lib/pages/payment_methods/widgets/card.dart
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
|
||||||
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class SelectedRecipientCard extends StatelessWidget {
|
||||||
|
final AppDimensions dimensions;
|
||||||
|
final Recipient recipient;
|
||||||
|
final VoidCallback onClear;
|
||||||
|
|
||||||
|
const SelectedRecipientCard({
|
||||||
|
super.key,
|
||||||
|
required this.dimensions,
|
||||||
|
required this.recipient,
|
||||||
|
required this.onClear,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
|
return Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: EdgeInsets.all(dimensions.paddingMedium),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SectionTitle(loc.recipient),
|
||||||
|
SizedBox(height: dimensions.paddingSmall),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
CircleAvatar(
|
||||||
|
child: Text(recipient.name.substring(0, 1).toUpperCase()),
|
||||||
|
),
|
||||||
|
SizedBox(width: dimensions.paddingMedium),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(recipient.name, style: theme.textTheme.titleMedium),
|
||||||
|
if (recipient.email.isNotEmpty)
|
||||||
|
Text(
|
||||||
|
recipient.email,
|
||||||
|
style: theme.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: theme.colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: onClear,
|
||||||
|
child: Text(loc.chooseAnotherRecipient),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,8 @@ import 'package:pweb/providers/payment_flow_provider.dart';
|
|||||||
import 'package:pweb/utils/dimensions.dart';
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
import 'package:pweb/utils/payment/selector_type.dart';
|
import 'package:pweb/utils/payment/selector_type.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentInfoSection extends StatelessWidget {
|
class PaymentInfoSection extends StatelessWidget {
|
||||||
final AppDimensions dimensions;
|
final AppDimensions dimensions;
|
||||||
@@ -27,13 +29,14 @@ class PaymentInfoSection extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
final hasRecipient = recipient != null;
|
final hasRecipient = recipient != null;
|
||||||
final availableTypes = hasRecipient
|
final availableTypes = hasRecipient
|
||||||
? pageSelector.getAvailablePaymentTypes()
|
? pageSelector.getAvailablePaymentTypes()
|
||||||
: {for (final type in PaymentType.values) type: type};
|
: {for (final type in PaymentType.values) type: type};
|
||||||
|
|
||||||
if (hasRecipient && availableTypes.isEmpty) {
|
if (hasRecipient && availableTypes.isEmpty) {
|
||||||
return const Text('This recipient has no available payment details.');
|
return Text(loc.recipientNoPaymentDetails);
|
||||||
}
|
}
|
||||||
|
|
||||||
final selectedType = flowProvider.selectedType;
|
final selectedType = flowProvider.selectedType;
|
||||||
@@ -41,7 +44,7 @@ class PaymentInfoSection extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const SectionTitle('Payment info'),
|
SectionTitle(loc.paymentInfo),
|
||||||
SizedBox(height: dimensions.paddingSmall),
|
SizedBox(height: dimensions.paddingSmall),
|
||||||
PaymentTypeSelector(
|
PaymentTypeSelector(
|
||||||
availableTypes: availableTypes,
|
availableTypes: availableTypes,
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import 'package:pweb/providers/payment_flow_provider.dart';
|
|||||||
import 'package:pweb/providers/payment_methods.dart';
|
import 'package:pweb/providers/payment_methods.dart';
|
||||||
import 'package:pweb/providers/recipient.dart';
|
import 'package:pweb/providers/recipient.dart';
|
||||||
import 'package:pweb/utils/dimensions.dart';
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentPageBody extends StatelessWidget {
|
class PaymentPageBody extends StatelessWidget {
|
||||||
@@ -45,13 +46,14 @@ class PaymentPageBody extends StatelessWidget {
|
|||||||
final recipientProvider = context.watch<RecipientProvider>();
|
final recipientProvider = context.watch<RecipientProvider>();
|
||||||
final flowProvider = context.watch<PaymentFlowProvider>();
|
final flowProvider = context.watch<PaymentFlowProvider>();
|
||||||
final recipient = pageSelector.selectedRecipient;
|
final recipient = pageSelector.selectedRecipient;
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
if (methodsProvider.isLoading) {
|
if (methodsProvider.isLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (methodsProvider.error != null) {
|
if (methodsProvider.error != null) {
|
||||||
return Center(child: Text('Error: ${methodsProvider.error}'));
|
return Center(child: Text(loc.notificationError(methodsProvider.error ?? loc.noErrorInformation)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Align(
|
return Align(
|
||||||
@@ -74,7 +76,7 @@ class PaymentPageBody extends StatelessWidget {
|
|||||||
PaymentHeader(),
|
PaymentHeader(),
|
||||||
SizedBox(height: dimensions.paddingXXLarge),
|
SizedBox(height: dimensions.paddingXXLarge),
|
||||||
|
|
||||||
const SectionTitle('Source of funds'),
|
SectionTitle(loc.sourceOfFunds),
|
||||||
SizedBox(height: dimensions.paddingSmall),
|
SizedBox(height: dimensions.paddingSmall),
|
||||||
PaymentMethodSelector(
|
PaymentMethodSelector(
|
||||||
methodsProvider: methodsProvider,
|
methodsProvider: methodsProvider,
|
||||||
|
|||||||
@@ -3,10 +3,14 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:pshared/models/recipient/recipient.dart';
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/address_book/page/search.dart';
|
import 'package:pweb/pages/address_book/page/search.dart';
|
||||||
|
import 'package:pweb/pages/payment_methods/widgets/card.dart';
|
||||||
|
import 'package:pweb/pages/payment_methods/widgets/search.dart';
|
||||||
import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
|
import 'package:pweb/pages/payment_methods/widgets/section_title.dart';
|
||||||
import 'package:pweb/providers/recipient.dart';
|
import 'package:pweb/providers/recipient.dart';
|
||||||
import 'package:pweb/utils/dimensions.dart';
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class RecipientSection extends StatelessWidget {
|
class RecipientSection extends StatelessWidget {
|
||||||
final Recipient? recipient;
|
final Recipient? recipient;
|
||||||
@@ -32,6 +36,7 @@ class RecipientSection extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
if (recipient != null) {
|
if (recipient != null) {
|
||||||
return SelectedRecipientCard(
|
return SelectedRecipientCard(
|
||||||
dimensions: dimensions,
|
dimensions: dimensions,
|
||||||
@@ -43,7 +48,7 @@ class RecipientSection extends StatelessWidget {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const SectionTitle('Recipient'),
|
SectionTitle(loc.recipient),
|
||||||
SizedBox(height: dimensions.paddingSmall),
|
SizedBox(height: dimensions.paddingSmall),
|
||||||
RecipientSearchField(
|
RecipientSearchField(
|
||||||
controller: searchController,
|
controller: searchController,
|
||||||
@@ -62,126 +67,3 @@ class RecipientSection extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SelectedRecipientCard extends StatelessWidget {
|
|
||||||
final AppDimensions dimensions;
|
|
||||||
final Recipient recipient;
|
|
||||||
final VoidCallback onClear;
|
|
||||||
|
|
||||||
const SelectedRecipientCard({
|
|
||||||
super.key,
|
|
||||||
required this.dimensions,
|
|
||||||
required this.recipient,
|
|
||||||
required this.onClear,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
padding: EdgeInsets.all(dimensions.paddingMedium),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: theme.colorScheme.surfaceVariant.withOpacity(0.4),
|
|
||||||
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
const SectionTitle('Recipient'),
|
|
||||||
SizedBox(height: dimensions.paddingSmall),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
CircleAvatar(
|
|
||||||
child: Text(recipient.name.substring(0, 1).toUpperCase()),
|
|
||||||
),
|
|
||||||
SizedBox(width: dimensions.paddingMedium),
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Text(recipient.name, style: theme.textTheme.titleMedium),
|
|
||||||
if (recipient.email.isNotEmpty)
|
|
||||||
Text(
|
|
||||||
recipient.email,
|
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
|
||||||
color: theme.colorScheme.onSurface.withOpacity(0.7),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TextButton(
|
|
||||||
onPressed: onClear,
|
|
||||||
child: const Text('Choose another recipient'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class RecipientSearchResults extends StatelessWidget {
|
|
||||||
final AppDimensions dimensions;
|
|
||||||
final RecipientProvider recipientProvider;
|
|
||||||
final ValueChanged<Recipient> onRecipientSelected;
|
|
||||||
|
|
||||||
const RecipientSearchResults({
|
|
||||||
super.key,
|
|
||||||
required this.dimensions,
|
|
||||||
required this.recipientProvider,
|
|
||||||
required this.onRecipientSelected,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (recipientProvider.isLoading) {
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipientProvider.error != null) {
|
|
||||||
return Text(
|
|
||||||
recipientProvider.error!,
|
|
||||||
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recipientProvider.recipients.isEmpty) {
|
|
||||||
return const Text('No recipients yet.');
|
|
||||||
}
|
|
||||||
|
|
||||||
final results = recipientProvider.filteredRecipients;
|
|
||||||
|
|
||||||
if (results.isEmpty) {
|
|
||||||
return const Text('No recipients found for this query.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return ConstrainedBox(
|
|
||||||
constraints: const BoxConstraints(maxHeight: 240),
|
|
||||||
child: ListView.separated(
|
|
||||||
shrinkWrap: true,
|
|
||||||
itemCount: results.length,
|
|
||||||
separatorBuilder: (_, __) => SizedBox(height: dimensions.paddingSmall),
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final recipient = results[index];
|
|
||||||
return ListTile(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
|
|
||||||
),
|
|
||||||
tileColor: Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.2),
|
|
||||||
leading: CircleAvatar(
|
|
||||||
child: Text(recipient.name.substring(0, 1).toUpperCase()),
|
|
||||||
),
|
|
||||||
title: Text(recipient.name),
|
|
||||||
subtitle: Text(recipient.email),
|
|
||||||
trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16),
|
|
||||||
onTap: () => onRecipientSelected(recipient),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
71
frontend/pweb/lib/pages/payment_methods/widgets/search.dart
Normal file
71
frontend/pweb/lib/pages/payment_methods/widgets/search.dart
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/recipient/recipient.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/providers/recipient.dart';
|
||||||
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class RecipientSearchResults extends StatelessWidget {
|
||||||
|
final AppDimensions dimensions;
|
||||||
|
final RecipientProvider recipientProvider;
|
||||||
|
final ValueChanged<Recipient> onRecipientSelected;
|
||||||
|
|
||||||
|
const RecipientSearchResults({
|
||||||
|
super.key,
|
||||||
|
required this.dimensions,
|
||||||
|
required this.recipientProvider,
|
||||||
|
required this.onRecipientSelected,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
if (recipientProvider.isLoading) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipientProvider.error != null) {
|
||||||
|
return Text(
|
||||||
|
loc.notificationError(recipientProvider.error ?? loc.noErrorInformation),
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipientProvider.recipients.isEmpty) {
|
||||||
|
return Text(loc.noRecipientsYet);
|
||||||
|
}
|
||||||
|
|
||||||
|
final results = recipientProvider.filteredRecipients;
|
||||||
|
|
||||||
|
if (results.isEmpty) {
|
||||||
|
return Text(loc.noRecipientsFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConstrainedBox(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 240),
|
||||||
|
child: ListView.separated(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: results.length,
|
||||||
|
separatorBuilder: (_, _) => SizedBox(height: dimensions.paddingSmall),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final recipient = results[index];
|
||||||
|
return ListTile(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
|
||||||
|
),
|
||||||
|
leading: CircleAvatar(
|
||||||
|
child: Text(recipient.name.substring(0, 1).toUpperCase()),
|
||||||
|
),
|
||||||
|
title: Text(recipient.name),
|
||||||
|
subtitle: Text(recipient.email),
|
||||||
|
trailing: const Icon(Icons.arrow_forward_ios_rounded, size: 16),
|
||||||
|
onTap: () => onRecipientSelected(recipient),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,11 +21,12 @@ class PaymentConfigController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addMethod() async {
|
Future<void> addMethod() async {
|
||||||
|
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||||
await showDialog<PaymentMethodData>(
|
await showDialog<PaymentMethodData>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => const AddPaymentMethodDialog(),
|
builder: (_) => const AddPaymentMethodDialog(),
|
||||||
);
|
);
|
||||||
loadMethods();
|
methodsProvider.loadMethods();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> editMethod(PaymentMethod method) async {
|
Future<void> editMethod(PaymentMethod method) async {
|
||||||
@@ -33,19 +34,20 @@ class PaymentConfigController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteMethod(PaymentMethod method) async {
|
Future<void> deleteMethod(PaymentMethod method) async {
|
||||||
|
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final confirmed = await showDialog<bool>(
|
final confirmed = await showDialog<bool>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => AlertDialog(
|
builder: (dialogContext) => AlertDialog(
|
||||||
title: Text(l10n.delete),
|
title: Text(l10n.delete),
|
||||||
content: Text(l10n.deletePaymentConfirmation),
|
content: Text(l10n.deletePaymentConfirmation),
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () => Navigator.pop(context, false),
|
onPressed: () => Navigator.pop(dialogContext, false),
|
||||||
child: Text(l10n.cancel),
|
child: Text(l10n.cancel),
|
||||||
),
|
),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => Navigator.pop(context, true),
|
onPressed: () => Navigator.pop(dialogContext, true),
|
||||||
child: Text(l10n.delete),
|
child: Text(l10n.delete),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -53,7 +55,7 @@ class PaymentConfigController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (confirmed == true) {
|
if (confirmed == true) {
|
||||||
context.read<PaymentMethodsProvider>().deleteMethod(method);
|
methodsProvider.deleteMethod(method);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pweb/models/wallet.dart';
|
import 'package:pweb/models/wallet.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/payout_page/methods/widget.dart';
|
import 'package:pweb/pages/payout_page/methods/widget.dart';
|
||||||
import 'package:pweb/pages/payout_page/wallet/wigets.dart';
|
import 'package:pweb/pages/payout_page/wallet/wigets.dart';
|
||||||
import 'package:pweb/providers/payment_methods.dart';
|
import 'package:pweb/providers/payment_methods.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentConfigPage extends StatelessWidget {
|
class PaymentConfigPage extends StatelessWidget {
|
||||||
final Function(Wallet) onWalletTap;
|
final Function(Wallet) onWalletTap;
|
||||||
@@ -16,13 +19,14 @@ class PaymentConfigPage extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final provider = context.watch<PaymentMethodsProvider>();
|
final provider = context.watch<PaymentMethodsProvider>();
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
if (provider.isLoading) {
|
if (provider.isLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (provider.error != null) {
|
if (provider.error != null) {
|
||||||
return Center(child: Text('Error: ${provider.error}'));
|
return Center(child: Text(loc.notificationError(provider.error ?? loc.noErrorInformation)));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
|
|||||||
@@ -3,25 +3,28 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:pweb/models/wallet.dart';
|
import 'package:pweb/models/wallet.dart';
|
||||||
import 'package:pweb/utils/dimensions.dart';
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class SaveWalletButton extends StatelessWidget {
|
class SaveWalletButton extends StatelessWidget {
|
||||||
final Wallet wallet;
|
final Wallet wallet;
|
||||||
final TextEditingController nameController;
|
final TextEditingController nameController;
|
||||||
final TextEditingController balanceController;
|
final TextEditingController balanceController;
|
||||||
final VoidCallback onSave; // Changed to VoidCallback
|
final VoidCallback onSave;
|
||||||
|
|
||||||
const SaveWalletButton({
|
const SaveWalletButton({
|
||||||
super.key,
|
super.key,
|
||||||
required this.wallet,
|
required this.wallet,
|
||||||
required this.nameController,
|
required this.nameController,
|
||||||
required this.balanceController,
|
required this.balanceController,
|
||||||
required this.onSave, // Now matches _saveWallet signature
|
required this.onSave,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final dimensions = AppDimensions();
|
final dimensions = AppDimensions();
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
return Center(
|
return Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
@@ -29,7 +32,7 @@ class SaveWalletButton extends StatelessWidget {
|
|||||||
height: dimensions.buttonHeight,
|
height: dimensions.buttonHeight,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
|
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
|
||||||
onTap: onSave, // Directly use onSave now
|
onTap: onSave,
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
@@ -37,7 +40,7 @@ class SaveWalletButton extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
'Save',
|
loc.save,
|
||||||
style: theme.textTheme.bodyLarge?.copyWith(
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
color: theme.colorScheme.onSecondary,
|
color: theme.colorScheme.onSecondary,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
|
|||||||
@@ -1,14 +1,19 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pweb/providers/page_selector.dart';
|
import 'package:pweb/providers/page_selector.dart';
|
||||||
import 'package:pweb/providers/wallets.dart';
|
import 'package:pweb/providers/wallets.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class SendPayoutButton extends StatelessWidget {
|
class SendPayoutButton extends StatelessWidget {
|
||||||
const SendPayoutButton({super.key});
|
const SendPayoutButton({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
return ElevatedButton(
|
return ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
shadowColor: null,
|
shadowColor: null,
|
||||||
@@ -23,7 +28,7 @@ class SendPayoutButton extends StatelessWidget {
|
|||||||
pageSelectorProvider.startPaymentFromWallet(wallet);
|
pageSelectorProvider.startPaymentFromWallet(wallet);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Text('Send Payout'),
|
child: Text(loc.payoutNavSendPayout),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class TopUpButton extends StatelessWidget{
|
class TopUpButton extends StatelessWidget{
|
||||||
const TopUpButton({super.key});
|
const TopUpButton({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
return ElevatedButton(
|
return ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
shadowColor: null,
|
shadowColor: null,
|
||||||
@@ -13,10 +16,10 @@ class TopUpButton extends StatelessWidget{
|
|||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text('Add functionality')),
|
SnackBar(content: Text(loc.addFunctionality)),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
child: Text('Top Up Balance'),
|
child: Text(loc.topUpBalance),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,8 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:pweb/providers/wallets.dart';
|
import 'package:pweb/providers/wallets.dart';
|
||||||
import 'package:pweb/widgets/error/snackbar.dart';
|
import 'package:pweb/widgets/error/snackbar.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class WalletEditHeader extends StatefulWidget {
|
class WalletEditHeader extends StatefulWidget {
|
||||||
const WalletEditHeader({super.key});
|
const WalletEditHeader({super.key});
|
||||||
@@ -33,6 +35,8 @@ class _WalletEditHeaderState extends State<WalletEditHeader> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final provider = context.watch<WalletsProvider>();
|
final provider = context.watch<WalletsProvider>();
|
||||||
final wallet = provider.selectedWallet;
|
final wallet = provider.selectedWallet;
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
final messanger = ScaffoldMessenger.of(context);
|
||||||
|
|
||||||
if (wallet == null) {
|
if (wallet == null) {
|
||||||
return SizedBox.shrink();
|
return SizedBox.shrink();
|
||||||
@@ -75,10 +79,10 @@ class _WalletEditHeaderState extends State<WalletEditHeader> {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
decoration: const InputDecoration(
|
decoration: InputDecoration(
|
||||||
border: OutlineInputBorder(),
|
border: OutlineInputBorder(),
|
||||||
isDense: true,
|
isDense: true,
|
||||||
hintText: 'Wallet name',
|
hintText: loc.walletName,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import 'package:pweb/pages/payout_page/wallet/history/history.dart';
|
|||||||
import 'package:pweb/providers/wallets.dart';
|
import 'package:pweb/providers/wallets.dart';
|
||||||
import 'package:pweb/utils/dimensions.dart';
|
import 'package:pweb/utils/dimensions.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class WalletEditPage extends StatelessWidget {
|
class WalletEditPage extends StatelessWidget {
|
||||||
final VoidCallback onBack;
|
final VoidCallback onBack;
|
||||||
@@ -18,13 +20,14 @@ class WalletEditPage extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final dimensions = AppDimensions();
|
final dimensions = AppDimensions();
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
return Consumer<WalletsProvider>(
|
return Consumer<WalletsProvider>(
|
||||||
builder: (context, provider, child) {
|
builder: (context, provider, child) {
|
||||||
final wallet = provider.selectedWallet;
|
final wallet = provider.selectedWallet;
|
||||||
|
|
||||||
if (wallet == null) {
|
if (wallet == null) {
|
||||||
return Center(child: Text('Кошелёк не выбран'));
|
return Center(child: Text(loc.noWalletSelected));
|
||||||
}
|
}
|
||||||
|
|
||||||
return Align(
|
return Align(
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:pshared/models/payment/status.dart';
|
import 'package:pshared/models/payment/status.dart';
|
||||||
@@ -7,6 +6,8 @@ import 'package:pshared/utils/localization.dart';
|
|||||||
import 'package:pweb/models/wallet_transaction.dart';
|
import 'package:pweb/models/wallet_transaction.dart';
|
||||||
import 'package:pweb/providers/wallet_transactions.dart';
|
import 'package:pweb/providers/wallet_transactions.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class WalletHistoryFilters extends StatelessWidget {
|
class WalletHistoryFilters extends StatelessWidget {
|
||||||
final WalletTransactionsProvider provider;
|
final WalletTransactionsProvider provider;
|
||||||
@@ -21,6 +22,7 @@ class WalletHistoryFilters extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
return Card(
|
return Card(
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
@@ -35,13 +37,13 @@ class WalletHistoryFilters extends StatelessWidget {
|
|||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Wallet activity',
|
loc.walletActivity,
|
||||||
style: theme.textTheme.titleMedium,
|
style: theme.textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
if (provider.hasFilters)
|
if (provider.hasFilters)
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: provider.resetFilters,
|
onPressed: provider.resetFilters,
|
||||||
child: const Text('Reset'),
|
child: Text(loc.reset),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -81,7 +83,7 @@ class WalletHistoryFilters extends StatelessWidget {
|
|||||||
icon: const Icon(Icons.date_range_outlined),
|
icon: const Icon(Icons.date_range_outlined),
|
||||||
label: Text(
|
label: Text(
|
||||||
provider.dateRange == null
|
provider.dateRange == null
|
||||||
? 'Select period'
|
? loc.selectPeriod
|
||||||
: '${dateToLocalFormat(context, provider.dateRange!.start)} – ${dateToLocalFormat(context, provider.dateRange!.end)}',
|
: '${dateToLocalFormat(context, provider.dateRange!.start)} – ${dateToLocalFormat(context, provider.dateRange!.end)}',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import 'package:pweb/pages/payout_page/wallet/history/filters.dart';
|
|||||||
import 'package:pweb/pages/payout_page/wallet/history/table.dart';
|
import 'package:pweb/pages/payout_page/wallet/history/table.dart';
|
||||||
import 'package:pweb/providers/wallet_transactions.dart';
|
import 'package:pweb/providers/wallet_transactions.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class WalletHistory extends StatefulWidget {
|
class WalletHistory extends StatefulWidget {
|
||||||
final Wallet wallet;
|
final Wallet wallet;
|
||||||
@@ -64,6 +66,7 @@ class _WalletHistoryState extends State<WalletHistory> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
return Consumer<WalletTransactionsProvider>(
|
return Consumer<WalletTransactionsProvider>(
|
||||||
builder: (context, provider, child) {
|
builder: (context, provider, child) {
|
||||||
@@ -81,16 +84,16 @@ class _WalletHistoryState extends State<WalletHistory> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
'Failed to load history',
|
loc.failedToLoadHistory,
|
||||||
style: theme.textTheme.titleMedium!
|
style: theme.textTheme.titleMedium!
|
||||||
.copyWith(color: theme.colorScheme.error),
|
.copyWith(color: theme.colorScheme.error),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(provider.error!),
|
Text(loc.notificationError(provider.error ?? loc.noErrorInformation)),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onPressed: _load,
|
onPressed: _load,
|
||||||
child: const Text('Retry'),
|
child: Text(loc.retry),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:pweb/models/wallet_transaction.dart';
|
import 'package:pweb/models/wallet_transaction.dart';
|
||||||
@@ -6,6 +5,8 @@ import 'package:pweb/pages/payout_page/wallet/history/chip.dart';
|
|||||||
import 'package:pweb/pages/report/table/badge.dart';
|
import 'package:pweb/pages/report/table/badge.dart';
|
||||||
import 'package:pweb/utils/currency.dart';
|
import 'package:pweb/utils/currency.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class WalletTransactionsTable extends StatelessWidget {
|
class WalletTransactionsTable extends StatelessWidget {
|
||||||
final List<WalletTransaction> transactions;
|
final List<WalletTransaction> transactions;
|
||||||
@@ -15,14 +16,15 @@ class WalletTransactionsTable extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
if (transactions.isEmpty) {
|
if (transactions.isEmpty) {
|
||||||
return Card(
|
return Card(
|
||||||
color: theme.colorScheme.onSecondary,
|
color: theme.colorScheme.onSecondary,
|
||||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||||
child: const Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Text('No history yet'),
|
child: Text(loc.walletHistoryEmpty),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -38,14 +40,14 @@ class WalletTransactionsTable extends StatelessWidget {
|
|||||||
child: DataTable(
|
child: DataTable(
|
||||||
columnSpacing: 18,
|
columnSpacing: 18,
|
||||||
headingTextStyle: const TextStyle(fontWeight: FontWeight.w600),
|
headingTextStyle: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
columns: const [
|
columns: [
|
||||||
DataColumn(label: Text('Status')),
|
DataColumn(label: Text(loc.colStatus)),
|
||||||
DataColumn(label: Text('Type')),
|
DataColumn(label: Text(loc.colType)),
|
||||||
DataColumn(label: Text('Amount')),
|
DataColumn(label: Text(loc.colAmount)),
|
||||||
DataColumn(label: Text('Balance')),
|
DataColumn(label: Text(loc.colBalance)),
|
||||||
DataColumn(label: Text('Counterparty')),
|
DataColumn(label: Text(loc.colCounterparty)),
|
||||||
DataColumn(label: Text('Date')),
|
DataColumn(label: Text(loc.colDate)),
|
||||||
DataColumn(label: Text('Comment')),
|
DataColumn(label: Text(loc.colComment)),
|
||||||
],
|
],
|
||||||
rows: List.generate(
|
rows: List.generate(
|
||||||
transactions.length,
|
transactions.length,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class PayoutDistributionChart extends StatelessWidget {
|
|||||||
// 1) Aggregate sums
|
// 1) Aggregate sums
|
||||||
final sums = <String, double>{};
|
final sums = <String, double>{};
|
||||||
for (var op in operations) {
|
for (var op in operations) {
|
||||||
final name = op.name ?? AppLocalizations.of(context)!.unknown;
|
final name = op.name;
|
||||||
sums[name] = (sums[name] ?? 0) + op.amount;
|
sums[name] = (sums[name] ?? 0) + op.amount;
|
||||||
}
|
}
|
||||||
if (sums.isEmpty) {
|
if (sums.isEmpty) {
|
||||||
@@ -32,7 +32,7 @@ class PayoutDistributionChart extends StatelessWidget {
|
|||||||
final palette = [
|
final palette = [
|
||||||
Theme.of(context).colorScheme.primary,
|
Theme.of(context).colorScheme.primary,
|
||||||
Theme.of(context).colorScheme.secondary,
|
Theme.of(context).colorScheme.secondary,
|
||||||
Theme.of(context).colorScheme.tertiary ?? Colors.grey,
|
Theme.of(context).colorScheme.tertiary,
|
||||||
Theme.of(context).colorScheme.primaryContainer,
|
Theme.of(context).colorScheme.primaryContainer,
|
||||||
Theme.of(context).colorScheme.secondaryContainer,
|
Theme.of(context).colorScheme.secondaryContainer,
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import 'package:pweb/pages/report/table/filters.dart';
|
|||||||
import 'package:pweb/pages/report/table/widget.dart';
|
import 'package:pweb/pages/report/table/widget.dart';
|
||||||
import 'package:pweb/providers/operatioins.dart';
|
import 'package:pweb/providers/operatioins.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class OperationHistoryPage extends StatefulWidget {
|
class OperationHistoryPage extends StatefulWidget {
|
||||||
const OperationHistoryPage({super.key});
|
const OperationHistoryPage({super.key});
|
||||||
@@ -48,6 +50,7 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
return Consumer<OperationProvider>(
|
return Consumer<OperationProvider>(
|
||||||
builder: (context, provider, child) {
|
builder: (context, provider, child) {
|
||||||
if (provider.isLoading) {
|
if (provider.isLoading) {
|
||||||
@@ -59,10 +62,10 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text('Error: ${provider.error}'),
|
Text(loc.notificationError(provider.error ?? loc.noErrorInformation)),
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () => provider.loadOperations(),
|
onPressed: () => provider.loadOperations(),
|
||||||
child: const Text('Retry'),
|
child: Text(loc.retry),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import 'package:pshared/provider/locale.dart';
|
import 'package:pshared/provider/locale.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
|
||||||
import 'package:pweb/services/amplitude.dart';
|
import 'package:pweb/services/amplitude.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class LocalePicker extends StatelessWidget {
|
class LocalePicker extends StatelessWidget {
|
||||||
final String title;
|
final String title;
|
||||||
@@ -74,11 +75,11 @@ class LocalePicker extends StatelessWidget {
|
|||||||
String _localizedLocaleName(Locale locale, AppLocalizations loc) {
|
String _localizedLocaleName(Locale locale, AppLocalizations loc) {
|
||||||
switch (locale.languageCode) {
|
switch (locale.languageCode) {
|
||||||
case 'en':
|
case 'en':
|
||||||
return 'English';
|
return loc.englishLanguage;
|
||||||
case 'ru':
|
case 'ru':
|
||||||
return 'Русский';
|
return loc.russianLanguage;
|
||||||
case 'de':
|
case 'de':
|
||||||
return 'Deutsch';
|
return loc.germanLanguage;
|
||||||
default:
|
default:
|
||||||
return locale.toString();
|
return locale.toString();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class ProfileSettingsPage extends StatelessWidget {
|
|||||||
errorText: loc.avatarUpdateError,
|
errorText: loc.avatarUpdateError,
|
||||||
),
|
),
|
||||||
AccountName(
|
AccountName(
|
||||||
name: 'User Name',
|
name: loc.userNamePlaceholder,
|
||||||
title: loc.accountName,
|
title: loc.accountName,
|
||||||
hintText: loc.accountNameHint,
|
hintText: loc.accountNameHint,
|
||||||
errorText: loc.accountNameUpdateError,
|
errorText: loc.accountNameUpdateError,
|
||||||
|
|||||||
@@ -78,14 +78,21 @@ class SelectValueTile<T> extends BaseEditTile<T> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: ListView(
|
child: RadioGroup<T>(
|
||||||
shrinkWrap: true,
|
groupValue: initial,
|
||||||
children: displayedOptions.map((o) => RadioListTile<T>(
|
onChanged: (value) {
|
||||||
value: o,
|
if (value != null && !isSaving) {
|
||||||
groupValue: initial,
|
onSave(value);
|
||||||
title: Text(labelBuilder(o)),
|
}
|
||||||
onChanged: isSaving ? null : (v) { if (v != null) onSave(v); },
|
},
|
||||||
)).toList(),
|
child: ListView(
|
||||||
|
shrinkWrap: true,
|
||||||
|
children: displayedOptions.map((o) => RadioListTile<T>(
|
||||||
|
value: o,
|
||||||
|
title: Text(labelBuilder(o)),
|
||||||
|
enabled: !isSaving,
|
||||||
|
)).toList(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
|
|||||||
@@ -8,8 +8,8 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import 'package:pshared/api/requests/login_data.dart';
|
import 'package:pshared/api/requests/login_data.dart';
|
||||||
import 'package:pshared/models/describable.dart';
|
import 'package:pshared/models/describable.dart';
|
||||||
import 'package:pshared/provider/account.dart';
|
|
||||||
import 'package:pshared/provider/locale.dart';
|
import 'package:pshared/provider/locale.dart';
|
||||||
|
import 'package:pshared/provider/account.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/pages.dart';
|
import 'package:pweb/app/router/pages.dart';
|
||||||
import 'package:pweb/pages/signup/form/content.dart';
|
import 'package:pweb/pages/signup/form/content.dart';
|
||||||
|
|||||||
105
frontend/pweb/lib/services/accounts.dart
Normal file
105
frontend/pweb/lib/services/accounts.dart
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/api/requests/login_data.dart';
|
||||||
|
import 'package:pshared/models/account/account.dart';
|
||||||
|
import 'package:pshared/models/describable.dart';
|
||||||
|
import 'package:pshared/models/storable.dart';
|
||||||
|
|
||||||
|
import 'mock_ids.dart';
|
||||||
|
|
||||||
|
class InvalidCredentialsException implements Exception {
|
||||||
|
@override
|
||||||
|
String toString() => 'InvalidCredentialsException';
|
||||||
|
}
|
||||||
|
|
||||||
|
class DuplicateAccountException implements Exception {
|
||||||
|
@override
|
||||||
|
String toString() => 'DuplicateAccountException';
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountLoginResult {
|
||||||
|
final Account account;
|
||||||
|
final String roleId;
|
||||||
|
|
||||||
|
const AccountLoginResult({
|
||||||
|
required this.account,
|
||||||
|
required this.roleId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccountRecord {
|
||||||
|
final Account account;
|
||||||
|
final String password;
|
||||||
|
final String roleId;
|
||||||
|
|
||||||
|
const _AccountRecord({
|
||||||
|
required this.account,
|
||||||
|
required this.password,
|
||||||
|
required this.roleId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountsService {
|
||||||
|
final List<_AccountRecord> _accounts = [
|
||||||
|
_AccountRecord(
|
||||||
|
account: Account(
|
||||||
|
storable: newStorable(id: companyAccountRef),
|
||||||
|
describable: newDescribable(name: 'Sendico Company'),
|
||||||
|
avatarUrl: null,
|
||||||
|
lastName: 'Owner',
|
||||||
|
login: 'company@sendico.com',
|
||||||
|
locale: 'ru',
|
||||||
|
),
|
||||||
|
password: 'password123A',
|
||||||
|
roleId: companyRoleId,
|
||||||
|
),
|
||||||
|
_AccountRecord(
|
||||||
|
account: Account(
|
||||||
|
storable: newStorable(id: recipientAccountRef),
|
||||||
|
describable: newDescribable(name: 'John Recipient'),
|
||||||
|
avatarUrl: null,
|
||||||
|
lastName: 'Doe',
|
||||||
|
login: 'recipient@sendico.com',
|
||||||
|
locale: 'ru',
|
||||||
|
),
|
||||||
|
password: 'password123A',
|
||||||
|
roleId: recipientRoleId,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
Future<AccountLoginResult> login(String email, String password, {String? locale}) async {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
|
final normalized = email.trim().toLowerCase();
|
||||||
|
final record = _accounts.where((acc) => acc.account.login.toLowerCase() == normalized).singleOrNull;
|
||||||
|
|
||||||
|
if (record == null || record.password != password) {
|
||||||
|
throw InvalidCredentialsException();
|
||||||
|
}
|
||||||
|
|
||||||
|
return AccountLoginResult(account: record.account, roleId: record.roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<AccountLoginResult> signup(AccountData data, {String roleId = recipientRoleId}) async {
|
||||||
|
await Future.delayed(const Duration(milliseconds: 300));
|
||||||
|
|
||||||
|
final normalized = data.login.trim().toLowerCase();
|
||||||
|
if (_accounts.any((acc) => acc.account.login.toLowerCase() == normalized)) {
|
||||||
|
throw DuplicateAccountException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final account = Account(
|
||||||
|
storable: newStorable(id: 'account-${_accounts.length + 1}'),
|
||||||
|
describable: newDescribable(name: data.name),
|
||||||
|
avatarUrl: null,
|
||||||
|
lastName: data.lastName,
|
||||||
|
login: normalized,
|
||||||
|
locale: data.locale,
|
||||||
|
);
|
||||||
|
|
||||||
|
_accounts.add(_AccountRecord(account: account, password: data.password, roleId: roleId));
|
||||||
|
return AccountLoginResult(account: account, roleId: roleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Account? getByRef(String accountRef) => _accounts.where((acc) => acc.account.id == accountRef).singleOrNull?.account;
|
||||||
|
}
|
||||||
18
frontend/pweb/lib/services/auth.dart
Normal file
18
frontend/pweb/lib/services/auth.dart
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
class AuthenticationService {
|
||||||
|
Future<bool> verifyTwoFactorCode(String code) async {
|
||||||
|
await Future.delayed(const Duration(seconds: 2));
|
||||||
|
|
||||||
|
if (code == '000000') {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw const WrongCodeException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class WrongCodeException implements Exception {
|
||||||
|
const WrongCodeException();
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'WrongCodeException';
|
||||||
|
}
|
||||||
12
frontend/pweb/lib/services/mock_ids.dart
Normal file
12
frontend/pweb/lib/services/mock_ids.dart
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
// Centralized identifiers for mock auth/permission data to keep the
|
||||||
|
// mock services in sync and make it easy to swap in a real API later.
|
||||||
|
const String mockOrganizationRef = 'org-sendico';
|
||||||
|
|
||||||
|
const String companyRoleId = 'role-company';
|
||||||
|
const String recipientRoleId = 'role-recipient';
|
||||||
|
|
||||||
|
const String companyAccountRef = 'account-company';
|
||||||
|
const String recipientAccountRef = 'account-recipient';
|
||||||
|
|
||||||
|
const String accountsPolicyDescriptionId = 'policy-accounts';
|
||||||
|
const String rolesPolicyDescriptionId = 'policy-roles';
|
||||||
@@ -5,6 +5,7 @@ import 'package:pshared/api/responses/error/server.dart';
|
|||||||
import 'package:pshared/config/constants.dart';
|
import 'package:pshared/config/constants.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
import 'package:pweb/services/accounts.dart';
|
||||||
|
|
||||||
|
|
||||||
class ErrorHandler {
|
class ErrorHandler {
|
||||||
@@ -48,6 +49,8 @@ class ErrorHandler {
|
|||||||
final errorHandlers = <Type, String Function(Object)>{
|
final errorHandlers = <Type, String Function(Object)>{
|
||||||
ErrorResponse: (ex) => _handleErrorResponseLocs(locs, ex as ErrorResponse),
|
ErrorResponse: (ex) => _handleErrorResponseLocs(locs, ex as ErrorResponse),
|
||||||
ConnectivityError: (ex) => _handleConnectivityErrorLocs(locs, ex as ConnectivityError),
|
ConnectivityError: (ex) => _handleConnectivityErrorLocs(locs, ex as ConnectivityError),
|
||||||
|
InvalidCredentialsException: (_) => locs.errorLoginUnauthorized,
|
||||||
|
DuplicateAccountException: (_) => locs.errorAccountExists,
|
||||||
};
|
};
|
||||||
|
|
||||||
return errorHandlers[e.runtimeType]?.call(e) ?? e.toString();
|
return errorHandlers[e.runtimeType]?.call(e) ?? e.toString();
|
||||||
|
|||||||
@@ -6,16 +6,19 @@ import 'package:cached_network_image/cached_network_image.dart';
|
|||||||
|
|
||||||
import 'package:pshared/provider/account.dart';
|
import 'package:pshared/provider/account.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class AccountAvatar extends StatelessWidget {
|
class AccountAvatar extends StatelessWidget {
|
||||||
const AccountAvatar({super.key});
|
const AccountAvatar({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
return Consumer<AccountProvider>(
|
return Consumer<AccountProvider>(
|
||||||
builder: (context, provider, _) => UserAccountsDrawerHeader(
|
builder: (context, provider, _) => UserAccountsDrawerHeader(
|
||||||
accountName: Text(provider.account?.name ?? 'John Doe'),
|
accountName: Text(provider.account?.name ?? loc.userNamePlaceholder),
|
||||||
accountEmail: Text(provider.account?.login ?? 'john.doe@acme.com'),
|
accountEmail: Text(provider.account?.login ?? loc.usernameHint),
|
||||||
currentAccountPicture: CircleAvatar(
|
currentAccountPicture: CircleAvatar(
|
||||||
backgroundImage: (provider.account?.avatarUrl?.isNotEmpty ?? false)
|
backgroundImage: (provider.account?.avatarUrl?.isNotEmpty ?? false)
|
||||||
? CachedNetworkImageProvider(provider.account!.avatarUrl!)
|
? CachedNetworkImageProvider(provider.account!.avatarUrl!)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ enum PayoutDestination {
|
|||||||
case PayoutDestination.addrecipient:
|
case PayoutDestination.addrecipient:
|
||||||
return loc.addRecipient;
|
return loc.addRecipient;
|
||||||
case PayoutDestination.editwallet:
|
case PayoutDestination.editwallet:
|
||||||
return 'Edit Wallet';
|
return loc.editWallet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:pshared/models/resources.dart';
|
||||||
|
|
||||||
import 'package:pshared/provider/account.dart';
|
import 'package:pshared/provider/account.dart';
|
||||||
|
import 'package:pshared/provider/permissions.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/address_book/form/page.dart';
|
import 'package:pweb/pages/address_book/form/page.dart';
|
||||||
import 'package:pweb/pages/address_book/page/page.dart';
|
import 'package:pweb/pages/address_book/page/page.dart';
|
||||||
@@ -13,25 +15,50 @@ import 'package:pweb/pages/report/page.dart';
|
|||||||
import 'package:pweb/pages/settings/profile/page.dart';
|
import 'package:pweb/pages/settings/profile/page.dart';
|
||||||
import 'package:pweb/pages/dashboard/dashboard.dart';
|
import 'package:pweb/pages/dashboard/dashboard.dart';
|
||||||
import 'package:pweb/providers/page_selector.dart';
|
import 'package:pweb/providers/page_selector.dart';
|
||||||
|
import 'package:pweb/app/router/pages.dart';
|
||||||
import 'package:pweb/widgets/appbar/app_bar.dart';
|
import 'package:pweb/widgets/appbar/app_bar.dart';
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
import 'package:pweb/widgets/sidebar/sidebar.dart';
|
import 'package:pweb/widgets/sidebar/sidebar.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PageSelector extends StatelessWidget {
|
class PageSelector extends StatelessWidget {
|
||||||
const PageSelector({super.key});
|
const PageSelector({super.key});
|
||||||
|
|
||||||
void _logout(BuildContext context) => context.read<AccountProvider>().logout();
|
void _logout(BuildContext context) {
|
||||||
|
context.read<AccountProvider>().logout();
|
||||||
|
navigateAndReplace(context, Pages.login);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Consumer<PageSelectorProvider>(builder:(context, provider, _) {
|
Widget build(BuildContext context) {
|
||||||
|
final provider = context.watch<PageSelectorProvider>();
|
||||||
|
final permissions = context.watch<PermissionsProvider>();
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
|
||||||
|
final bool restrictedAccess = !permissions.canRead(ResourceType.chainWallets);
|
||||||
|
final allowedDestinations = restrictedAccess
|
||||||
|
? <PayoutDestination>{
|
||||||
|
PayoutDestination.settings,
|
||||||
|
PayoutDestination.methods,
|
||||||
|
PayoutDestination.editwallet,
|
||||||
|
}
|
||||||
|
: PayoutDestination.values.toSet();
|
||||||
|
|
||||||
|
final selected = allowedDestinations.contains(provider.selected)
|
||||||
|
? provider.selected
|
||||||
|
: (restrictedAccess ? PayoutDestination.settings : PayoutDestination.dashboard);
|
||||||
|
|
||||||
|
if (selected != provider.selected) {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => provider.selectPage(selected));
|
||||||
|
}
|
||||||
|
|
||||||
Widget content;
|
Widget content;
|
||||||
switch (provider.selected) {
|
switch (selected) {
|
||||||
case PayoutDestination.dashboard:
|
case PayoutDestination.dashboard:
|
||||||
content = DashboardPage(
|
content = DashboardPage(
|
||||||
onRecipientSelected: (recipient) =>
|
onRecipientSelected: (recipient) => provider.selectRecipient(recipient),
|
||||||
provider.selectRecipient(recipient),
|
|
||||||
onGoToPaymentWithoutRecipient: provider.startPaymentWithoutRecipient,
|
onGoToPaymentWithoutRecipient: provider.startPaymentWithoutRecipient,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
@@ -79,16 +106,16 @@ class PageSelector extends StatelessWidget {
|
|||||||
? WalletEditPage(
|
? WalletEditPage(
|
||||||
onBack: provider.goBackFromWalletEdit,
|
onBack: provider.goBackFromWalletEdit,
|
||||||
)
|
)
|
||||||
: const Center(child: Text('No wallet selected')); //TODO Localize
|
: Center(child: Text(loc.noWalletSelected));
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
content = Text(provider.selected.name);
|
content = Text(selected.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: PayoutAppBar(
|
appBar: PayoutAppBar(
|
||||||
title: Text(provider.selected.localizedLabel(context)),
|
title: Text(selected.localizedLabel(context)),
|
||||||
onAddFundsPressed: () {},
|
onAddFundsPressed: () {},
|
||||||
onLogout: () => _logout(context),
|
onLogout: () => _logout(context),
|
||||||
),
|
),
|
||||||
@@ -99,7 +126,7 @@ class PageSelector extends StatelessWidget {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
PayoutSidebar(
|
PayoutSidebar(
|
||||||
selected: provider.selected,
|
selected: selected,
|
||||||
onSelected: provider.selectPage,
|
onSelected: provider.selectPage,
|
||||||
onLogout: () => _logout(context),
|
onLogout: () => _logout(context),
|
||||||
),
|
),
|
||||||
@@ -108,5 +135,5 @@ class PageSelector extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class PayoutSidebar extends StatelessWidget {
|
|||||||
this.onLogout,
|
this.onLogout,
|
||||||
this.userName,
|
this.userName,
|
||||||
this.avatarUrl,
|
this.avatarUrl,
|
||||||
|
this.items,
|
||||||
});
|
});
|
||||||
|
|
||||||
final PayoutDestination selected;
|
final PayoutDestination selected;
|
||||||
@@ -21,11 +22,13 @@ class PayoutSidebar extends StatelessWidget {
|
|||||||
|
|
||||||
final String? userName;
|
final String? userName;
|
||||||
final String? avatarUrl;
|
final String? avatarUrl;
|
||||||
|
final List<PayoutDestination>? items;
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final items = [
|
final menuItems = items ??
|
||||||
|
<PayoutDestination>[
|
||||||
PayoutDestination.dashboard,
|
PayoutDestination.dashboard,
|
||||||
PayoutDestination.recipients,
|
PayoutDestination.recipients,
|
||||||
PayoutDestination.methods,
|
PayoutDestination.methods,
|
||||||
@@ -49,7 +52,7 @@ class PayoutSidebar extends StatelessWidget {
|
|||||||
theme: theme,
|
theme: theme,
|
||||||
avatarUrl: avatarUrl,
|
avatarUrl: avatarUrl,
|
||||||
userName: userName,
|
userName: userName,
|
||||||
items: items,
|
items: menuItems,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
onSelected: onSelected,
|
onSelected: onSelected,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class UserProfileCard extends StatelessWidget {
|
class UserProfileCard extends StatelessWidget {
|
||||||
final ThemeData theme;
|
final ThemeData theme;
|
||||||
@@ -21,6 +23,7 @@ class UserProfileCard extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
bool isSelected = selected == PayoutDestination.settings;
|
bool isSelected = selected == PayoutDestination.settings;
|
||||||
final backgroundColor = isSelected
|
final backgroundColor = isSelected
|
||||||
? theme.colorScheme.primaryContainer
|
? theme.colorScheme.primaryContainer
|
||||||
@@ -52,7 +55,7 @@ class UserProfileCard extends StatelessWidget {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
userName ?? 'User Name',
|
userName ?? loc.userNamePlaceholder,
|
||||||
style: theme.textTheme.bodyLarge?.copyWith(
|
style: theme.textTheme.bodyLarge?.copyWith(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"ru": [
|
"ru": [
|
||||||
"errorAccountExists",
|
|
||||||
"companyName",
|
"companyName",
|
||||||
"companynameRequired",
|
"companynameRequired",
|
||||||
"errorSignUp",
|
"errorSignUp",
|
||||||
|
|||||||
Reference in New Issue
Block a user