Added Localizations and ran small fixes #6
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')
|
||||
clients,
|
||||
|
||||
/// Represents comments on tasks or other resources
|
||||
@JsonValue('comments')
|
||||
comments,
|
||||
|
||||
/// Represents invitations sent to users
|
||||
@JsonValue('invitations')
|
||||
invitations,
|
||||
@@ -46,6 +42,9 @@ enum ResourceType {
|
||||
@JsonValue('organizations')
|
||||
organizations,
|
||||
|
||||
@JsonValue('chain_wallets')
|
||||
chainWallets,
|
||||
|
||||
/// Represents permissions service
|
||||
@JsonValue('permissions')
|
||||
permissions,
|
||||
@@ -54,25 +53,6 @@ enum ResourceType {
|
||||
@JsonValue('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
|
||||
@JsonValue('refresh_tokens')
|
||||
refreshTokens,
|
||||
@@ -88,20 +68,4 @@ enum ResourceType {
|
||||
/// Represents steps in workflows or processes
|
||||
@JsonValue('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
|
||||
Widget build(BuildContext context) => Consumer<T>(builder: (context, provider, _) {
|
||||
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.isEmpty) return empty ?? Text('Empty data'); //TODO: need to implement localizations too
|
||||
if (provider.error != null) return error ?? Text('Error while loading data. Try again');
|
||||
if (provider.isEmpty) return empty ?? Text('Empty data');
|
||||
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 {
|
||||
static const String appName = String.fromEnvironment(
|
||||
'APP_NAME',
|
||||
defaultValue: 'SendiCo',
|
||||
defaultValue: 'sendico',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,9 +36,9 @@
|
||||
"signupError": "Failed to signup: {error}",
|
||||
"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.",
|
||||
"errorAccountExists": "Account already exists",
|
||||
"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",
|
||||
"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",
|
||||
"errorVerificationTokenNotFound": "Account for verification not found. Sign up again",
|
||||
"created": "Created",
|
||||
@@ -441,5 +441,36 @@
|
||||
"verificationStatusErrorUnknown": "Unexpected error occurred while verification. Try once again or contact support",
|
||||
"accountVerified": "Account Verified!",
|
||||
"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}. Проверьте ваше интернет-соединение и попробуйте снова.",
|
||||
"errorAccountNotVerified": "Ваш аккаунт еще не подтвержден. Пожалуйста, проверьте вашу электронную почту для завершения верификации",
|
||||
"errorLoginUnauthorized": "Неверный логин или пароль. Пожалуйста, попробуйте снова",
|
||||
"errorAccountExists": "Аккаунт с таким логином уже существует",
|
||||
"errorInternalError": "Произошла внутренняя ошибка. Мы в курсе проблемы и работаем над ее решением. Пожалуйста, попробуйте позже",
|
||||
"errorVerificationTokenNotFound": "Аккаунт для верификации не найден. Зарегистрируйтесь снова",
|
||||
"created": "Создано",
|
||||
@@ -433,5 +434,36 @@
|
||||
"verificationStatusErrorUnknown": "Произошла непредвиденная ошибка при подтверждении. Попробуйте еще раз или обратитесь в службу поддержки",
|
||||
"accountVerified": "Аккаунт подтвержден!",
|
||||
"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:pshared/config/constants.dart';
|
||||
import 'package:pshared/provider/account.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:pweb/app/app.dart';
|
||||
@@ -63,6 +64,11 @@ void main() async {
|
||||
create: (_) => TwoFactorProvider(),
|
||||
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: (_) => CarouselIndexProvider()),
|
||||
|
||||
|
||||
@@ -1,18 +1,22 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'package:pshared/models/payment/status.dart';
|
||||
|
||||
import 'package:pweb/models/currency.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
enum WalletTransactionType { topUp, payout }
|
||||
|
||||
extension WalletTransactionTypeX on WalletTransactionType {
|
||||
String label(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
switch (this) {
|
||||
case WalletTransactionType.topUp:
|
||||
return 'Top up';
|
||||
return loc.walletTopUp;
|
||||
case WalletTransactionType.payout:
|
||||
return 'Payout';
|
||||
return loc.payout;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ 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: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';
|
||||
@@ -14,7 +14,7 @@ import 'package:pweb/providers/recipient.dart';
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class RecipientAddressBookPage extends StatelessWidget {
|
||||
class RecipientAddressBookPage extends StatefulWidget {
|
||||
final ValueChanged<Recipient> onRecipientSelected;
|
||||
final VoidCallback onAddRecipient;
|
||||
final ValueChanged<Recipient>? onEditRecipient;
|
||||
@@ -31,32 +31,65 @@ class RecipientAddressBookPage extends StatelessWidget {
|
||||
static const double _bigBox = 30;
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final provider = context.watch<RecipientProvider>();
|
||||
_syncSearchField(provider);
|
||||
|
||||
if (provider.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator()); //TODO This should be in the provider
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (provider.error != null) {
|
||||
return Center(child: Text('Error: ${provider.error}'));
|
||||
return Center(child: Text(loc.notificationError(provider.error ?? loc.noErrorInformation)));
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
RecipientAddressBookHeader(onAddRecipient: onAddRecipient),
|
||||
const SizedBox(height: _smallBox),
|
||||
RecipientAddressBookHeader(onAddRecipient: widget.onAddRecipient),
|
||||
const SizedBox(height: RecipientAddressBookPage._smallBox),
|
||||
RecipientSearchField(
|
||||
controller: TextEditingController(text: provider.query),
|
||||
focusNode: FocusNode(),
|
||||
controller: _searchController,
|
||||
focusNode: _searchFocusNode,
|
||||
onChanged: provider.setQuery,
|
||||
),
|
||||
const SizedBox(height: _bigBox),
|
||||
const SizedBox(height: RecipientAddressBookPage._bigBox),
|
||||
Row(
|
||||
children: [
|
||||
RecipientFilterButton(
|
||||
@@ -86,17 +119,17 @@ class RecipientAddressBookPage extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
SizedBox(
|
||||
height: _expandedHeight,
|
||||
height: RecipientAddressBookPage._expandedHeight,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(_paddingAll),
|
||||
padding: const EdgeInsets.all(RecipientAddressBookPage._paddingAll),
|
||||
child: RecipientAddressBookList(
|
||||
filteredRecipients: provider.filteredRecipients,
|
||||
onEdit: (recipient) => onEditRecipient?.call(recipient),
|
||||
onSelected: onRecipientSelected,
|
||||
onEdit: (recipient) => widget.onEditRecipient?.call(recipient),
|
||||
onSelected: widget.onRecipientSelected,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class BalanceAddFunds extends StatelessWidget {
|
||||
final VoidCallback onTopUp;
|
||||
@@ -19,6 +21,7 @@ class BalanceAddFunds extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
return InkWell(
|
||||
onTap: onTopUp,
|
||||
@@ -37,7 +40,7 @@ class BalanceAddFunds extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(width: _spacingMedium),
|
||||
Text(
|
||||
'Add funds',
|
||||
loc.addFunds,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.primary,
|
||||
fontWeight: FontWeight.w500,
|
||||
|
||||
@@ -5,6 +5,8 @@ import 'package:provider/provider.dart';
|
||||
import 'package:pweb/pages/dashboard/buttons/balance/carousel.dart';
|
||||
import 'package:pweb/providers/wallets.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class BalanceWidget extends StatelessWidget {
|
||||
const BalanceWidget({super.key});
|
||||
@@ -12,6 +14,7 @@ class BalanceWidget extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final walletsProvider = context.watch<WalletsProvider>();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
if (walletsProvider.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
@@ -20,7 +23,7 @@ class BalanceWidget extends StatelessWidget {
|
||||
final wallets = walletsProvider.wallets;
|
||||
|
||||
if (wallets == null || wallets.isEmpty) {
|
||||
return const Center(child: Text('No wallets available'));
|
||||
return Center(child: Text(loc.noWalletsAvailable));
|
||||
}
|
||||
|
||||
return
|
||||
@@ -29,4 +32,4 @@ class BalanceWidget extends StatelessWidget {
|
||||
onWalletChanged: walletsProvider.selectWallet,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ class UploadHistorySection extends StatelessWidget {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (provider.error != null) {
|
||||
return Text("Error: ${provider.error}");
|
||||
return Text(l10.notificationError(provider.error ?? l10.noErrorInformation));
|
||||
}
|
||||
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/providers/recipient.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class AdressBookPayout extends StatefulWidget {
|
||||
final ValueChanged<Recipient> onSelected;
|
||||
@@ -54,6 +56,7 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final provider = context.watch<RecipientProvider>();
|
||||
|
||||
if (provider.isLoading) {
|
||||
@@ -61,7 +64,7 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
|
||||
}
|
||||
|
||||
if (provider.error != null) {
|
||||
return Center(child: Text('Error: ${provider.error}'));
|
||||
return Center(child: Text(loc.notificationError(provider.error ?? loc.noErrorInformation)));
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
@@ -100,4 +103,4 @@ class _AdressBookPayoutState extends State<AdressBookPayout> {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,13 +26,10 @@ class AccountLoader extends StatelessWidget {
|
||||
);
|
||||
navigateAndReplace(context, Pages.login);
|
||||
}
|
||||
if ((provider.error == null) && (provider.account == null)) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
provider.restore();
|
||||
});
|
||||
if (provider.account == null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => navigateAndReplace(context, Pages.login));
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return child;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/account.dart';
|
||||
import 'package:pshared/provider/permissions.dart';
|
||||
|
||||
import 'package:pweb/app/router/pages.dart';
|
||||
@@ -16,8 +17,10 @@ class PermissionsLoader extends StatelessWidget {
|
||||
const PermissionsLoader({super.key, required this.child});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Consumer<PermissionsProvider>(builder: (context, provider, _) {
|
||||
if (provider.isLoading) return const Center(child: CircularProgressIndicator());
|
||||
Widget build(BuildContext context) => Consumer2<PermissionsProvider, AccountProvider>(builder: (context, provider, accountProvider, _) {
|
||||
if (provider.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
if (provider.error != null) {
|
||||
postNotifyUserOfErrorX(
|
||||
context: context,
|
||||
@@ -26,7 +29,7 @@ class PermissionsLoader extends StatelessWidget {
|
||||
);
|
||||
navigateAndReplace(context, Pages.login);
|
||||
}
|
||||
if ((provider.error == null) && (provider.permissions.isEmpty)) {
|
||||
if (provider.error == null && !provider.isReady && accountProvider.account != null) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
provider.load();
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ class PaymentMethodTypeSelector extends StatelessWidget {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
|
||||
return DropdownButtonFormField<PaymentType>(
|
||||
value: value,
|
||||
initialValue: value,
|
||||
decoration: InputDecoration(labelText: l10n.paymentType),
|
||||
items: PaymentType.values.map((type) {
|
||||
final label = getPaymentTypeLabel(context, type);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/pages/payment_methods/icon.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';
|
||||
|
||||
|
||||
class PaymentMethodTile extends StatelessWidget {
|
||||
const PaymentMethodTile({
|
||||
super.key,
|
||||
@@ -62,7 +64,7 @@ class PaymentMethodTile extends StatelessWidget {
|
||||
Widget _buildMakeMainButton(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return IconButton(
|
||||
tooltip: 'Make main',
|
||||
tooltip: AppLocalizations.of(context)!.makeMain,
|
||||
icon: Icon(
|
||||
method.isMain ? Icons.star : Icons.star_outline,
|
||||
color: method.isMain ? theme.colorScheme.primary : null,
|
||||
@@ -97,4 +99,4 @@ class PaymentMethodTile extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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/payment/selector_type.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PaymentInfoSection extends StatelessWidget {
|
||||
final AppDimensions dimensions;
|
||||
@@ -27,13 +29,14 @@ class PaymentInfoSection extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final hasRecipient = recipient != null;
|
||||
final availableTypes = hasRecipient
|
||||
? pageSelector.getAvailablePaymentTypes()
|
||||
: {for (final type in PaymentType.values) type: type};
|
||||
|
||||
if (hasRecipient && availableTypes.isEmpty) {
|
||||
return const Text('This recipient has no available payment details.');
|
||||
return Text(loc.recipientNoPaymentDetails);
|
||||
}
|
||||
|
||||
final selectedType = flowProvider.selectedType;
|
||||
@@ -41,7 +44,7 @@ class PaymentInfoSection extends StatelessWidget {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SectionTitle('Payment info'),
|
||||
SectionTitle(loc.paymentInfo),
|
||||
SizedBox(height: dimensions.paddingSmall),
|
||||
PaymentTypeSelector(
|
||||
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/recipient.dart';
|
||||
import 'package:pweb/utils/dimensions.dart';
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PaymentPageBody extends StatelessWidget {
|
||||
@@ -45,13 +46,14 @@ class PaymentPageBody extends StatelessWidget {
|
||||
final recipientProvider = context.watch<RecipientProvider>();
|
||||
final flowProvider = context.watch<PaymentFlowProvider>();
|
||||
final recipient = pageSelector.selectedRecipient;
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
if (methodsProvider.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (methodsProvider.error != null) {
|
||||
return Center(child: Text('Error: ${methodsProvider.error}'));
|
||||
return Center(child: Text(loc.notificationError(methodsProvider.error ?? loc.noErrorInformation)));
|
||||
}
|
||||
|
||||
return Align(
|
||||
@@ -74,7 +76,7 @@ class PaymentPageBody extends StatelessWidget {
|
||||
PaymentHeader(),
|
||||
SizedBox(height: dimensions.paddingXXLarge),
|
||||
|
||||
const SectionTitle('Source of funds'),
|
||||
SectionTitle(loc.sourceOfFunds),
|
||||
SizedBox(height: dimensions.paddingSmall),
|
||||
PaymentMethodSelector(
|
||||
methodsProvider: methodsProvider,
|
||||
|
||||
@@ -3,10 +3,14 @@ import 'package:flutter/material.dart';
|
||||
import 'package:pshared/models/recipient/recipient.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/providers/recipient.dart';
|
||||
import 'package:pweb/utils/dimensions.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class RecipientSection extends StatelessWidget {
|
||||
final Recipient? recipient;
|
||||
@@ -32,6 +36,7 @@ class RecipientSection extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
if (recipient != null) {
|
||||
return SelectedRecipientCard(
|
||||
dimensions: dimensions,
|
||||
@@ -43,7 +48,7 @@ class RecipientSection extends StatelessWidget {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
const SectionTitle('Recipient'),
|
||||
SectionTitle(loc.recipient),
|
||||
SizedBox(height: dimensions.paddingSmall),
|
||||
RecipientSearchField(
|
||||
controller: searchController,
|
||||
@@ -61,127 +66,4 @@ 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 {
|
||||
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||
await showDialog<PaymentMethodData>(
|
||||
context: context,
|
||||
builder: (_) => const AddPaymentMethodDialog(),
|
||||
);
|
||||
loadMethods();
|
||||
methodsProvider.loadMethods();
|
||||
}
|
||||
|
||||
Future<void> editMethod(PaymentMethod method) async {
|
||||
@@ -33,19 +34,20 @@ class PaymentConfigController {
|
||||
}
|
||||
|
||||
Future<void> deleteMethod(PaymentMethod method) async {
|
||||
final methodsProvider = context.read<PaymentMethodsProvider>();
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (_) => AlertDialog(
|
||||
builder: (dialogContext) => AlertDialog(
|
||||
title: Text(l10n.delete),
|
||||
content: Text(l10n.deletePaymentConfirmation),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
onPressed: () => Navigator.pop(dialogContext, false),
|
||||
child: Text(l10n.cancel),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
onPressed: () => Navigator.pop(dialogContext, true),
|
||||
child: Text(l10n.delete),
|
||||
),
|
||||
],
|
||||
@@ -53,7 +55,7 @@ class PaymentConfigController {
|
||||
);
|
||||
|
||||
if (confirmed == true) {
|
||||
context.read<PaymentMethodsProvider>().deleteMethod(method);
|
||||
methodsProvider.deleteMethod(method);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pweb/models/wallet.dart';
|
||||
|
||||
import 'package:pweb/pages/payout_page/methods/widget.dart';
|
||||
import 'package:pweb/pages/payout_page/wallet/wigets.dart';
|
||||
import 'package:pweb/providers/payment_methods.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PaymentConfigPage extends StatelessWidget {
|
||||
final Function(Wallet) onWalletTap;
|
||||
@@ -16,13 +19,14 @@ class PaymentConfigPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final provider = context.watch<PaymentMethodsProvider>();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
if (provider.isLoading) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
if (provider.error != null) {
|
||||
return Center(child: Text('Error: ${provider.error}'));
|
||||
return Center(child: Text(loc.notificationError(provider.error ?? loc.noErrorInformation)));
|
||||
}
|
||||
|
||||
return Column(
|
||||
@@ -34,4 +38,4 @@ class PaymentConfigPage extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,25 +3,28 @@ import 'package:flutter/material.dart';
|
||||
import 'package:pweb/models/wallet.dart';
|
||||
import 'package:pweb/utils/dimensions.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class SaveWalletButton extends StatelessWidget {
|
||||
final Wallet wallet;
|
||||
final TextEditingController nameController;
|
||||
final TextEditingController balanceController;
|
||||
final VoidCallback onSave; // Changed to VoidCallback
|
||||
final VoidCallback onSave;
|
||||
|
||||
const SaveWalletButton({
|
||||
super.key,
|
||||
required this.wallet,
|
||||
required this.nameController,
|
||||
required this.balanceController,
|
||||
required this.onSave, // Now matches _saveWallet signature
|
||||
required this.onSave,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final dimensions = AppDimensions();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
return Center(
|
||||
child: SizedBox(
|
||||
@@ -29,7 +32,7 @@ class SaveWalletButton extends StatelessWidget {
|
||||
height: dimensions.buttonHeight,
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(dimensions.borderRadiusSmall),
|
||||
onTap: onSave, // Directly use onSave now
|
||||
onTap: onSave,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: theme.colorScheme.primary,
|
||||
@@ -37,7 +40,7 @@ class SaveWalletButton extends StatelessWidget {
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'Save',
|
||||
loc.save,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
color: theme.colorScheme.onSecondary,
|
||||
fontWeight: FontWeight.w600,
|
||||
@@ -49,4 +52,4 @@ class SaveWalletButton extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pweb/providers/page_selector.dart';
|
||||
import 'package:pweb/providers/wallets.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class SendPayoutButton extends StatelessWidget {
|
||||
const SendPayoutButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shadowColor: null,
|
||||
@@ -23,7 +28,7 @@ class SendPayoutButton extends StatelessWidget {
|
||||
pageSelectorProvider.startPaymentFromWallet(wallet);
|
||||
}
|
||||
},
|
||||
child: Text('Send Payout'),
|
||||
child: Text(loc.payoutNavSendPayout),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class TopUpButton extends StatelessWidget{
|
||||
const TopUpButton({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
return ElevatedButton(
|
||||
style: ElevatedButton.styleFrom(
|
||||
shadowColor: null,
|
||||
@@ -13,10 +16,10 @@ class TopUpButton extends StatelessWidget{
|
||||
),
|
||||
onPressed: () {
|
||||
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/widgets/error/snackbar.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletEditHeader extends StatefulWidget {
|
||||
const WalletEditHeader({super.key});
|
||||
@@ -33,7 +35,9 @@ class _WalletEditHeaderState extends State<WalletEditHeader> {
|
||||
Widget build(BuildContext context) {
|
||||
final provider = context.watch<WalletsProvider>();
|
||||
final wallet = provider.selectedWallet;
|
||||
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
final messanger = ScaffoldMessenger.of(context);
|
||||
|
||||
if (wallet == null) {
|
||||
return SizedBox.shrink();
|
||||
}
|
||||
@@ -75,10 +79,10 @@ class _WalletEditHeaderState extends State<WalletEditHeader> {
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
controller: _controller,
|
||||
decoration: const InputDecoration(
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
isDense: true,
|
||||
hintText: 'Wallet name',
|
||||
hintText: loc.walletName,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -112,4 +116,4 @@ class _WalletEditHeaderState extends State<WalletEditHeader> {
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import 'package:pweb/pages/payout_page/wallet/history/history.dart';
|
||||
import 'package:pweb/providers/wallets.dart';
|
||||
import 'package:pweb/utils/dimensions.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletEditPage extends StatelessWidget {
|
||||
final VoidCallback onBack;
|
||||
@@ -18,13 +20,14 @@ class WalletEditPage extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final dimensions = AppDimensions();
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
return Consumer<WalletsProvider>(
|
||||
builder: (context, provider, child) {
|
||||
final wallet = provider.selectedWallet;
|
||||
|
||||
if (wallet == null) {
|
||||
return Center(child: Text('Кошелёк не выбран'));
|
||||
return Center(child: Text(loc.noWalletSelected));
|
||||
}
|
||||
|
||||
return Align(
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'package:flutter/material.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/providers/wallet_transactions.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletHistoryFilters extends StatelessWidget {
|
||||
final WalletTransactionsProvider provider;
|
||||
@@ -21,6 +22,7 @@ class WalletHistoryFilters extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
@@ -35,13 +37,13 @@ class WalletHistoryFilters extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'Wallet activity',
|
||||
loc.walletActivity,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
if (provider.hasFilters)
|
||||
TextButton(
|
||||
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),
|
||||
label: Text(
|
||||
provider.dateRange == null
|
||||
? 'Select period'
|
||||
? loc.selectPeriod
|
||||
: '${dateToLocalFormat(context, provider.dateRange!.start)} – ${dateToLocalFormat(context, provider.dateRange!.end)}',
|
||||
),
|
||||
),
|
||||
@@ -91,4 +93,4 @@ class WalletHistoryFilters extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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/providers/wallet_transactions.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletHistory extends StatefulWidget {
|
||||
final Wallet wallet;
|
||||
@@ -64,6 +66,7 @@ class _WalletHistoryState extends State<WalletHistory> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
return Consumer<WalletTransactionsProvider>(
|
||||
builder: (context, provider, child) {
|
||||
@@ -81,16 +84,16 @@ class _WalletHistoryState extends State<WalletHistory> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Failed to load history',
|
||||
loc.failedToLoadHistory,
|
||||
style: theme.textTheme.titleMedium!
|
||||
.copyWith(color: theme.colorScheme.error),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(provider.error!),
|
||||
Text(loc.notificationError(provider.error ?? loc.noErrorInformation)),
|
||||
const SizedBox(height: 8),
|
||||
OutlinedButton(
|
||||
onPressed: _load,
|
||||
child: const Text('Retry'),
|
||||
child: Text(loc.retry),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -113,4 +116,4 @@ class _WalletHistoryState extends State<WalletHistory> {
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'package:flutter/material.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/utils/currency.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class WalletTransactionsTable extends StatelessWidget {
|
||||
final List<WalletTransaction> transactions;
|
||||
@@ -15,14 +16,15 @@ class WalletTransactionsTable extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
|
||||
if (transactions.isEmpty) {
|
||||
return Card(
|
||||
color: theme.colorScheme.onSecondary,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(16),
|
||||
child: Text('No history yet'),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Text(loc.walletHistoryEmpty),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -38,14 +40,14 @@ class WalletTransactionsTable extends StatelessWidget {
|
||||
child: DataTable(
|
||||
columnSpacing: 18,
|
||||
headingTextStyle: const TextStyle(fontWeight: FontWeight.w600),
|
||||
columns: const [
|
||||
DataColumn(label: Text('Status')),
|
||||
DataColumn(label: Text('Type')),
|
||||
DataColumn(label: Text('Amount')),
|
||||
DataColumn(label: Text('Balance')),
|
||||
DataColumn(label: Text('Counterparty')),
|
||||
DataColumn(label: Text('Date')),
|
||||
DataColumn(label: Text('Comment')),
|
||||
columns: [
|
||||
DataColumn(label: Text(loc.colStatus)),
|
||||
DataColumn(label: Text(loc.colType)),
|
||||
DataColumn(label: Text(loc.colAmount)),
|
||||
DataColumn(label: Text(loc.colBalance)),
|
||||
DataColumn(label: Text(loc.colCounterparty)),
|
||||
DataColumn(label: Text(loc.colDate)),
|
||||
DataColumn(label: Text(loc.colComment)),
|
||||
],
|
||||
rows: List.generate(
|
||||
transactions.length,
|
||||
@@ -85,4 +87,4 @@ class WalletTransactionsTable extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ class PayoutDistributionChart extends StatelessWidget {
|
||||
// 1) Aggregate sums
|
||||
final sums = <String, double>{};
|
||||
for (var op in operations) {
|
||||
final name = op.name ?? AppLocalizations.of(context)!.unknown;
|
||||
final name = op.name;
|
||||
sums[name] = (sums[name] ?? 0) + op.amount;
|
||||
}
|
||||
if (sums.isEmpty) {
|
||||
@@ -32,7 +32,7 @@ class PayoutDistributionChart extends StatelessWidget {
|
||||
final palette = [
|
||||
Theme.of(context).colorScheme.primary,
|
||||
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.secondaryContainer,
|
||||
];
|
||||
|
||||
@@ -8,6 +8,8 @@ import 'package:pweb/pages/report/table/filters.dart';
|
||||
import 'package:pweb/pages/report/table/widget.dart';
|
||||
import 'package:pweb/providers/operatioins.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class OperationHistoryPage extends StatefulWidget {
|
||||
const OperationHistoryPage({super.key});
|
||||
@@ -48,6 +50,7 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
return Consumer<OperationProvider>(
|
||||
builder: (context, provider, child) {
|
||||
if (provider.isLoading) {
|
||||
@@ -59,10 +62,10 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Error: ${provider.error}'),
|
||||
Text(loc.notificationError(provider.error ?? loc.noErrorInformation)),
|
||||
ElevatedButton(
|
||||
onPressed: () => provider.loadOperations(),
|
||||
child: const Text('Retry'),
|
||||
child: Text(loc.retry),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -108,4 +111,4 @@ class _OperationHistoryPageState extends State<OperationHistoryPage> {
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,10 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/provider/locale.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
import 'package:pweb/services/amplitude.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class LocalePicker extends StatelessWidget {
|
||||
final String title;
|
||||
@@ -74,13 +75,13 @@ class LocalePicker extends StatelessWidget {
|
||||
String _localizedLocaleName(Locale locale, AppLocalizations loc) {
|
||||
switch (locale.languageCode) {
|
||||
case 'en':
|
||||
return 'English';
|
||||
return loc.englishLanguage;
|
||||
case 'ru':
|
||||
return 'Русский';
|
||||
return loc.russianLanguage;
|
||||
case 'de':
|
||||
return 'Deutsch';
|
||||
return loc.germanLanguage;
|
||||
default:
|
||||
return locale.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ class ProfileSettingsPage extends StatelessWidget {
|
||||
errorText: loc.avatarUpdateError,
|
||||
),
|
||||
AccountName(
|
||||
name: 'User Name',
|
||||
name: loc.userNamePlaceholder,
|
||||
title: loc.accountName,
|
||||
hintText: loc.accountNameHint,
|
||||
errorText: loc.accountNameUpdateError,
|
||||
@@ -50,4 +50,4 @@ class ProfileSettingsPage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,14 +78,21 @@ class SelectValueTile<T> extends BaseEditTile<T> {
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: displayedOptions.map((o) => RadioListTile<T>(
|
||||
value: o,
|
||||
groupValue: initial,
|
||||
title: Text(labelBuilder(o)),
|
||||
onChanged: isSaving ? null : (v) { if (v != null) onSave(v); },
|
||||
)).toList(),
|
||||
child: RadioGroup<T>(
|
||||
groupValue: initial,
|
||||
onChanged: (value) {
|
||||
if (value != null && !isSaving) {
|
||||
onSave(value);
|
||||
}
|
||||
},
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
children: displayedOptions.map((o) => RadioListTile<T>(
|
||||
value: o,
|
||||
title: Text(labelBuilder(o)),
|
||||
enabled: !isSaving,
|
||||
)).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(),
|
||||
|
||||
@@ -8,8 +8,8 @@ import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:pshared/api/requests/login_data.dart';
|
||||
import 'package:pshared/models/describable.dart';
|
||||
import 'package:pshared/provider/account.dart';
|
||||
import 'package:pshared/provider/locale.dart';
|
||||
import 'package:pshared/provider/account.dart';
|
||||
|
||||
import 'package:pweb/app/router/pages.dart';
|
||||
import 'package:pweb/pages/signup/form/content.dart';
|
||||
|
||||
@@ -71,4 +71,4 @@ class TwoFactorProvider extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
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:pweb/generated/i18n/app_localizations.dart';
|
||||
import 'package:pweb/services/accounts.dart';
|
||||
|
||||
|
||||
class ErrorHandler {
|
||||
@@ -48,6 +49,8 @@ class ErrorHandler {
|
||||
final errorHandlers = <Type, String Function(Object)>{
|
||||
ErrorResponse: (ex) => _handleErrorResponseLocs(locs, ex as ErrorResponse),
|
||||
ConnectivityError: (ex) => _handleConnectivityErrorLocs(locs, ex as ConnectivityError),
|
||||
InvalidCredentialsException: (_) => locs.errorLoginUnauthorized,
|
||||
DuplicateAccountException: (_) => locs.errorAccountExists,
|
||||
};
|
||||
|
||||
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:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class AccountAvatar extends StatelessWidget {
|
||||
const AccountAvatar({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
return Consumer<AccountProvider>(
|
||||
builder: (context, provider, _) => UserAccountsDrawerHeader(
|
||||
accountName: Text(provider.account?.name ?? 'John Doe'),
|
||||
accountEmail: Text(provider.account?.login ?? 'john.doe@acme.com'),
|
||||
accountName: Text(provider.account?.name ?? loc.userNamePlaceholder),
|
||||
accountEmail: Text(provider.account?.login ?? loc.usernameHint),
|
||||
currentAccountPicture: CircleAvatar(
|
||||
backgroundImage: (provider.account?.avatarUrl?.isNotEmpty ?? false)
|
||||
? CachedNetworkImageProvider(provider.account!.avatarUrl!)
|
||||
|
||||
@@ -13,4 +13,4 @@ T? protectedWidgetctx<T extends Widget>(BuildContext context, ResourceType resou
|
||||
|
||||
T? protectedWidget<T extends Widget>(PermissionsProvider provider, ResourceType resource, T child, {perm.Action? action}) {
|
||||
return provider.canAccessResource(resource, action: action) ? child : null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ enum PayoutDestination {
|
||||
case PayoutDestination.addrecipient:
|
||||
return loc.addRecipient;
|
||||
case PayoutDestination.editwallet:
|
||||
return 'Edit Wallet';
|
||||
return loc.editWallet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:pshared/models/resources.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/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/dashboard/dashboard.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/sidebar/destinations.dart';
|
||||
import 'package:pweb/widgets/sidebar/sidebar.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class PageSelector extends StatelessWidget {
|
||||
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
|
||||
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;
|
||||
switch (provider.selected) {
|
||||
switch (selected) {
|
||||
case PayoutDestination.dashboard:
|
||||
content = DashboardPage(
|
||||
onRecipientSelected: (recipient) =>
|
||||
provider.selectRecipient(recipient),
|
||||
onRecipientSelected: (recipient) => provider.selectRecipient(recipient),
|
||||
onGoToPaymentWithoutRecipient: provider.startPaymentWithoutRecipient,
|
||||
);
|
||||
break;
|
||||
@@ -79,16 +106,16 @@ class PageSelector extends StatelessWidget {
|
||||
? WalletEditPage(
|
||||
onBack: provider.goBackFromWalletEdit,
|
||||
)
|
||||
: const Center(child: Text('No wallet selected')); //TODO Localize
|
||||
: Center(child: Text(loc.noWalletSelected));
|
||||
break;
|
||||
|
||||
default:
|
||||
content = Text(provider.selected.name);
|
||||
content = Text(selected.name);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: PayoutAppBar(
|
||||
title: Text(provider.selected.localizedLabel(context)),
|
||||
title: Text(selected.localizedLabel(context)),
|
||||
onAddFundsPressed: () {},
|
||||
onLogout: () => _logout(context),
|
||||
),
|
||||
@@ -99,7 +126,7 @@ class PageSelector extends StatelessWidget {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
PayoutSidebar(
|
||||
selected: provider.selected,
|
||||
selected: selected,
|
||||
onSelected: provider.selectPage,
|
||||
onLogout: () => _logout(context),
|
||||
),
|
||||
@@ -108,5 +135,5 @@ class PageSelector extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ class PayoutSidebar extends StatelessWidget {
|
||||
this.onLogout,
|
||||
this.userName,
|
||||
this.avatarUrl,
|
||||
this.items,
|
||||
});
|
||||
|
||||
final PayoutDestination selected;
|
||||
@@ -21,11 +22,13 @@ class PayoutSidebar extends StatelessWidget {
|
||||
|
||||
final String? userName;
|
||||
final String? avatarUrl;
|
||||
final List<PayoutDestination>? items;
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final items = [
|
||||
final menuItems = items ??
|
||||
<PayoutDestination>[
|
||||
PayoutDestination.dashboard,
|
||||
PayoutDestination.recipients,
|
||||
PayoutDestination.methods,
|
||||
@@ -49,11 +52,11 @@ class PayoutSidebar extends StatelessWidget {
|
||||
theme: theme,
|
||||
avatarUrl: avatarUrl,
|
||||
userName: userName,
|
||||
items: items,
|
||||
items: menuItems,
|
||||
selected: selected,
|
||||
onSelected: onSelected,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||
|
||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||
|
||||
|
||||
class UserProfileCard extends StatelessWidget {
|
||||
final ThemeData theme;
|
||||
@@ -21,6 +23,7 @@ class UserProfileCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final loc = AppLocalizations.of(context)!;
|
||||
bool isSelected = selected == PayoutDestination.settings;
|
||||
final backgroundColor = isSelected
|
||||
? theme.colorScheme.primaryContainer
|
||||
@@ -52,7 +55,7 @@ class UserProfileCard extends StatelessWidget {
|
||||
const SizedBox(width: 8),
|
||||
Flexible(
|
||||
child: Text(
|
||||
userName ?? 'User Name',
|
||||
userName ?? loc.userNamePlaceholder,
|
||||
style: theme.textTheme.bodyLarge?.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
@@ -67,4 +70,4 @@ class UserProfileCard extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 2.5 KiB |
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"ru": [
|
||||
"errorAccountExists",
|
||||
"companyName",
|
||||
"companynameRequired",
|
||||
"errorSignUp",
|
||||
|
||||
Reference in New Issue
Block a user