small fixes

This commit is contained in:
Arseni
2026-02-11 15:26:11 +03:00
parent edb43f9909
commit 0a1e04d0d6
7 changed files with 138 additions and 53 deletions

View File

@@ -11,10 +11,10 @@ import (
type pendingLoginResponse struct {
Account accountResponse `json:"account"`
PendingToken TokenData `json:"pendingToken"`
Destination string `json:"destination"`
Target string `json:"target"`
}
func LoginPending(logger mlogger.Logger, account *model.Account, pendingToken *TokenData, destination string) http.HandlerFunc {
func LoginPending(logger mlogger.Logger, account *model.Account, pendingToken *TokenData, target string) http.HandlerFunc {
return response.Accepted(
logger,
&pendingLoginResponse{
@@ -23,7 +23,7 @@ func LoginPending(logger mlogger.Logger, account *model.Account, pendingToken *T
authResponse: authResponse{},
},
PendingToken: *pendingToken,
Destination: destination,
Target: target,
},
)
}

View File

@@ -10,7 +10,6 @@ part 'login_pending.g.dart';
class PendingLoginResponse {
final AccountResponse account;
final TokenData pendingToken;
@JsonKey(name: 'destination')
final String target;
const PendingLoginResponse({

View File

@@ -15,19 +15,9 @@ class EmailVerificationProvider extends ChangeNotifier {
Exception? get error => _resource.error;
ErrorResponse? get errorResponse =>
_resource.error is ErrorResponse ? _resource.error as ErrorResponse : null;
bool get canResendVerification {
final err = errorResponse;
if (err == null) return false;
switch (err.error) {
case 'not_found':
case 'token_expired':
case 'data_conflict':
case 'internal_error':
return true;
default:
return false;
}
}
int? get errorCode => errorResponse?.code;
bool get canResendVerification =>
errorCode == 400 || errorCode == 410 || errorCode == 500;
Future<void> verify(String token) async {
final trimmed = token.trim();

View File

@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
import 'package:collection/collection.dart';
import 'package:share_plus/share_plus.dart';
import 'package:pshared/config/constants.dart';
import 'package:pshared/models/organization/organization.dart';
import 'package:pshared/provider/resource.dart';
@@ -88,4 +90,55 @@ class OrganizationsProvider extends ChangeNotifier {
// Best-effort cleanup of stored selection to avoid using stale org on next login.
await SecureStorageService.delete(Constants.currentOrgKey);
}
Future<Organization> uploadLogo(XFile logoFile) async {
if (!isOrganizationSet) {
throw StateError('Organization is not set');
}
_setResource(_resource.copyWith(isLoading: true, error: null));
try {
final updated = await OrganizationService.uploadLogoAndUpdate(current, logoFile);
final updatedList = organizations
.map((org) => org.id == updated.id ? updated : org)
.toList(growable: false);
_setResource(Resource(data: updatedList, isLoading: false));
_currentOrg = updated.id;
return updated;
} catch (e) {
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
rethrow;
}
}
Future<Organization> updateCurrent({
String? name,
String? description,
String? timeZone,
String? logoUrl,
}) async {
if (!isOrganizationSet) {
throw StateError('Organization is not set');
}
_setResource(_resource.copyWith(isLoading: true, error: null));
try {
final updated = await OrganizationService.updateSettings(
current,
name: name,
description: description,
timeZone: timeZone,
logoUrl: logoUrl,
);
final updatedList = organizations
.map((org) => org.id == updated.id ? updated : org)
.toList(growable: false);
_setResource(Resource(data: updatedList, isLoading: false));
_currentOrg = updated.id;
return updated;
} catch (e) {
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
rethrow;
}
}
}

View File

@@ -1,5 +1,3 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:uuid/uuid.dart';
@@ -23,9 +21,6 @@ class MultiQuotationProvider extends ChangeNotifier {
List<PaymentIntent>? _lastIntents;
bool _lastPreviewOnly = false;
Map<String, String>? _lastMetadata;
Timer? _autoRefreshTimer;
static const Duration _autoRefreshLead = Duration(seconds: 5);
Resource<PaymentQuotes> get resource => _quotation;
PaymentQuotes? get quotation => _quotation.data;
@@ -86,7 +81,6 @@ class MultiQuotationProvider extends ChangeNotifier {
? null
: Map<String, String>.from(metadata);
_cancelAutoRefresh();
_setResource(_quotation.copyWith(isLoading: true, error: null));
try {
final response = await MultiplePaymentsService.getQuotation(
@@ -102,7 +96,6 @@ class MultiQuotationProvider extends ChangeNotifier {
_setResource(
_quotation.copyWith(data: response, isLoading: false, error: null),
);
_scheduleAutoRefresh();
} catch (e) {
_setResource(
_quotation.copyWith(
@@ -131,7 +124,6 @@ class MultiQuotationProvider extends ChangeNotifier {
_lastIntents = null;
_lastPreviewOnly = false;
_lastMetadata = null;
_cancelAutoRefresh();
_quotation = Resource(data: null);
notifyListeners();
}
@@ -141,36 +133,8 @@ class MultiQuotationProvider extends ChangeNotifier {
notifyListeners();
}
void _scheduleAutoRefresh() {
_autoRefreshTimer?.cancel();
final expiresAt = quoteExpiresAt;
if (expiresAt == null) return;
final now = DateTime.now().toUtc();
var delay = expiresAt.difference(now) - _autoRefreshLead;
if (delay.isNegative) delay = Duration.zero;
_autoRefreshTimer = Timer(delay, _triggerAutoRefresh);
}
Future<void> _triggerAutoRefresh() async {
if (_quotation.isLoading) return;
final intents = _lastIntents;
if (intents == null || intents.isEmpty) return;
await quotePayments(
intents,
previewOnly: _lastPreviewOnly,
metadata: _lastMetadata,
);
}
void _cancelAutoRefresh() {
_autoRefreshTimer?.cancel();
_autoRefreshTimer = null;
}
@override
void dispose() {
_cancelAutoRefresh();
super.dispose();
}
}

View File

@@ -24,6 +24,7 @@ import 'package:pweb/app/router/payout_routes.dart';
import 'package:pweb/controllers/multiple_payouts.dart';
import 'package:pweb/controllers/payment_page.dart';
import 'package:pweb/providers/multiple_payouts.dart';
import 'package:pweb/controllers/multi_quotation.dart';
import 'package:pweb/providers/quotation/quotation.dart';
import 'package:pshared/models/payment/wallet.dart';
import 'package:pweb/pages/address_book/form/page.dart';
@@ -44,6 +45,7 @@ import 'package:pweb/services/payments/csv_input.dart';
import 'package:pweb/generated/i18n/app_localizations.dart';
RouteBase payoutShellRoute() => ShellRoute(
builder: (context, state, child) => MultiProvider(
providers: [
@@ -138,6 +140,13 @@ RouteBase payoutShellRoute() => ShellRoute(
update: (context, organization, provider) =>
provider!..update(organization),
),
ChangeNotifierProxyProvider<
MultiQuotationProvider,
MultiQuotationController
>(
create: (_) => MultiQuotationController(),
update: (_, quotation, controller) => controller!..update(quotation),
),
ChangeNotifierProxyProvider2<
OrganizationsProvider,
MultiQuotationProvider,

View File

@@ -0,0 +1,70 @@
import 'package:flutter/foundation.dart';
import 'package:pshared/provider/payment/multiple/quotation.dart';
import 'package:pweb/providers/quotation/auto_refresh.dart';
class MultiQuotationController extends ChangeNotifier {
static const Duration _autoRefreshLead = Duration(seconds: 5);
MultiQuotationProvider? _quotation;
final QuotationAutoRefreshController _autoRefreshController =
QuotationAutoRefreshController();
void update(MultiQuotationProvider quotation) {
if (identical(_quotation, quotation)) return;
_quotation?.removeListener(_handleQuotationChanged);
_quotation = quotation;
_quotation?.addListener(_handleQuotationChanged);
_handleQuotationChanged();
}
bool get isLoading => _quotation?.isLoading ?? false;
Exception? get error => _quotation?.error;
bool get canRefresh => _quotation?.canRefresh ?? false;
bool get isReady => _quotation?.isReady ?? false;
DateTime? get quoteExpiresAt => _quotation?.quoteExpiresAt;
void refreshQuotation() {
_quotation?.refreshQuotation();
}
void _handleQuotationChanged() {
_syncAutoRefresh();
notifyListeners();
}
void _syncAutoRefresh() {
final quotation = _quotation;
if (quotation == null) {
_autoRefreshController.reset();
return;
}
final expiresAt = quoteExpiresAt;
final scheduledAt = expiresAt == null
? null
: expiresAt.subtract(_autoRefreshLead);
_autoRefreshController.setEnabled(true);
_autoRefreshController.sync(
isLoading: quotation.isLoading,
canRefresh: quotation.canRefresh,
expiresAt: scheduledAt,
onRefresh: _refreshQuotation,
);
}
Future<void> _refreshQuotation() async {
await _quotation?.refreshQuotation();
}
@override
void dispose() {
_quotation?.removeListener(_handleQuotationChanged);
_autoRefreshController.dispose();
super.dispose();
}
}