Frontend first draft

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

View File

@@ -0,0 +1,210 @@
import 'package:amplitude_flutter/amplitude.dart';
import 'package:amplitude_flutter/configuration.dart';
import 'package:amplitude_flutter/constants.dart' as amp;
import 'package:amplitude_flutter/events/base_event.dart';
import 'package:flutter/widgets.dart';
import 'package:pshared/models/account/account.dart';
import 'package:pshared/models/payment/type.dart';
import 'package:pshared/models/recipient/status.dart';
import 'package:pshared/models/recipient/type.dart';
import 'package:pweb/widgets/sidebar/destinations.dart';
class AmplitudeService {
static late Amplitude _analytics;
static Amplitude _amp() => _analytics;
static Future<void> initialize() async {
_analytics = Amplitude(Configuration(
apiKey: '12345', //TODO define through App Contants
serverZone: amp.ServerZone.eu, //TODO define through App Contants
));
await _analytics.isBuilt;
}
static Future<void> identify(Account account) async =>
_amp().setUserId(account.id);
static Future<void> login(Account account) async =>
_logEvent(
'login',
userProperties: {
// 'email': account.email, TODO Add email into account
'locale': account.locale,
},
);
static Future<void> logout() async => _logEvent("logout");
static Future<void> pageOpened(PayoutDestination page, {String? path, String? uiSource}) async {
return _logEvent("pageOpened", eventProperties: {
"page": page,
if (path != null) "path": path,
if (uiSource != null) "uiSource": uiSource,
});
}
//TODO Add when registration is ready. User properties {user_id, registration_date, has_wallet (true/false), wallet_balance (should concider loggin it as: 0 / <100 / 100500 / 500+), preferred_method (Wallet/Card/Bank/IBAN), total_transactions, total_amount, last_payout_date, last_login_date , marketing_source}
// static Future<void> registrationStarted(String method, String country) async =>
// _logEvent("registrationStarted", eventProperties: {"method": method, "country": country});
// static Future<void> registrationCompleted(String method, String country) async =>
// _logEvent("registrationCompleted", eventProperties: {"method": method, "country": country});
static Future<void> pageNotFound(String url) async =>
_logEvent("pageNotFound", eventProperties: {"url": url});
static Future<void> localeChanged(Locale locale) async =>
_logEvent("localeChanged", eventProperties: {"locale": locale.toString()});
static Future<void> localeMatched(String locale, bool haveRequested) async => //DO we need it?
_logEvent("localeMatched", eventProperties: {
"locale": locale,
"have_requested_locale": haveRequested
});
static Future<void> recipientAddStarted() async =>
_logEvent("recipientAddStarted");
static Future<void> recipientAddCompleted(
RecipientType type,
RecipientStatus status,
Set<PaymentType> methods,
) async {
_logEvent(
"recipientAddCompleted",
eventProperties: {
"methods": methods.map((m) => m.name).toList(),
"type": type.name,
"status": status.name,
},
);
}
static Future<void> _paymentEvent(
String evt,
double amount,
double fee,
bool payerCoversFee,
PaymentType source,
PaymentType recpientPaymentMethod, {
String? message,
String? errorType,
Map<String, dynamic>? extraProps,
}) async {
final props = {
"amount": amount,
"fee": fee,
"feeCoveredBy": payerCoversFee ? 'payer' : 'recipient',
"source": source,
"recipient_method": recpientPaymentMethod,
if (message != null) "message": message,
if (errorType != null) "error_type": errorType,
if (extraProps != null) ...extraProps,
};
return _logEvent(evt, eventProperties: props);
}
static Future<void> paymentPrepared(double amount, double fee,
bool payerCoversFee, PaymentType source, PaymentType recpientPaymentMethod) async =>
_paymentEvent("paymentPrepared", amount, fee, payerCoversFee, source, recpientPaymentMethod);
//TODO Rework paymentStarted (do i need all those properties or is the event enough? Mb properties should be passed at paymentPrepared)
static Future<void> paymentStarted(double amount, double fee,
bool payerCoversFee, PaymentType source, PaymentType recpientPaymentMethod) async =>
_paymentEvent("paymentStarted", amount, fee, payerCoversFee, source, recpientPaymentMethod);
static Future<void> paymentFailed(double amount, double fee, bool payerCoversFee,
PaymentType source, PaymentType recpientPaymentMethod, String errorType, String message) async =>
_paymentEvent("paymentFailed", amount, fee, payerCoversFee, source, recpientPaymentMethod,
errorType: errorType, message: message);
static Future<void> paymentError(double amount, double fee, bool payerCoversFee,
PaymentType source,PaymentType recpientPaymentMethod, String message) async =>
_paymentEvent("paymentError", amount, fee, payerCoversFee, source, recpientPaymentMethod,
message: message);
static Future<void> paymentSuccess({
required double amount,
required double fee,
required bool payerCoversFee,
required PaymentType source,
required PaymentType recpientPaymentMethod,
required String transactionId,
String? comment,
required int durationMs,
}) async {
return _paymentEvent(
"paymentSuccess",
amount,
fee,
payerCoversFee,
source,
recpientPaymentMethod,
message: comment,
extraProps: {
"transaction_id": transactionId,
"duration_ms": durationMs, //How do i calculate duration here?
"\$revenue": amount, //How do i calculate revenue here?
"\$revenueType": "payment", //Do we need to get revenue type?
},
);
}
//TODO add when support is ready
// static Future<void> supportOpened(String fromPage, String trigger) async =>
// _logEvent("supportOpened", eventProperties: {"from_page": fromPage, "trigger": trigger});
// static Future<void> supportMessageSent(String category, bool resolved) async =>
// _logEvent("supportMessageSent", eventProperties: {"category": category, "resolved": resolved});
static Future<void> walletTopUp(double amount, PaymentType method) async =>
_logEvent("walletTopUp", eventProperties: {"amount": amount, "method": method});
//TODO Decide do we need uiElementClicked or pageOpened is enough?
static Future<void> uiElementClicked(String elementName, String page, String uiSource) async =>
_logEvent("uiElementClicked", eventProperties: {
"element_name": elementName,
"page": page,
"uiSource": uiSource
});
static final Map<String, int> _stepStartTimes = {};
//TODO Consider it as part of payment flow or registration flow or adding recipient and rework accordingly
static Future<void> stepStarted(String stepName, {String? context}) async {
_stepStartTimes[stepName] = DateTime.now().millisecondsSinceEpoch;
return _logEvent("stepStarted", eventProperties: {
"step_name": stepName,
if (context != null) "context": context,
});
}
static Future<void> stepCompleted(String stepName, bool success) async {
final now = DateTime.now().millisecondsSinceEpoch;
final start = _stepStartTimes[stepName] ?? now;
final duration = now - start;
return _logEvent("stepCompleted", eventProperties: {
"step_name": stepName,
"duration_ms": duration,
"success": success
});
}
static Future<void> _logEvent(
String eventType, {
Map<String, dynamic>? eventProperties,
Map<String, dynamic>? userProperties,
}) async {
final event = BaseEvent(
eventType,
eventProperties: eventProperties,
userProperties: userProperties,
);
_amp().track(event);
print(event.toString()); //TODO delete when everything is ready
}
}

View File

@@ -0,0 +1,12 @@
class AuthenticationService {
Future<bool> verifyTwoFactorCode(String code) async {
await Future.delayed(const Duration(seconds: 2));
if (code == '000000') {
return true;
} else {
throw Exception('Wrong Code'); //TODO Localize
}
}
}

View File

@@ -0,0 +1,22 @@
abstract class BalanceService {
Future<double> getBalance();
Future<String> getWalletName();
Future<String> getWalletId();
}
class MockBalanceService implements BalanceService {
@override
Future<double> getBalance() async {
return 3000000.56;
}
@override
Future<String> getWalletName() async {
return "Wallet";
}
@override
Future<String> getWalletId() async {
return "WA-12345667";
}
}

View File

@@ -0,0 +1,42 @@
import 'package:pshared/models/payment/methods/type.dart';
import 'package:pshared/models/payment/type.dart';
abstract class PaymentMethodsService {
Future<List<PaymentMethod>> fetchMethods();
}
class MockPaymentMethodsService implements PaymentMethodsService {
@override
Future<List<PaymentMethod>> fetchMethods() async {
await Future.delayed(const Duration(milliseconds: 200));
return [
PaymentMethod(
id: '1',
label: 'My account',
details: '•••4567',
type: PaymentType.bankAccount,
isMain: true,
),
PaymentMethod(
id: '2',
label: 'Euro IBAN',
details: 'DE•• •••8901',
type: PaymentType.iban,
),
PaymentMethod(
id: '3',
label: 'Wallet',
details: 'WA12345667',
type: PaymentType.wallet,
),
PaymentMethod(
id: '4',
label: 'Credit Card',
details: '21•• •••• •••• 8901',
type: PaymentType.card,
),
];
}
}

View File

@@ -0,0 +1,20 @@
import 'package:pshared/models/payment/upload_history_item.dart';
abstract class UploadHistoryService {
Future<List<UploadHistoryItem>> fetchHistory();
}
class MockUploadHistoryService implements UploadHistoryService {
@override
Future<List<UploadHistoryItem>> fetchHistory() async {
await Future.delayed(const Duration(milliseconds: 300));
return [
UploadHistoryItem(name: "cards_payout_single.csv", status: "Valid", time: "5 hours ago"),
UploadHistoryItem(name: "rfba_norm.csv", status: "Valid", time: "Yesterday"),
UploadHistoryItem(name: "iban (4).csv", status: "Valid", time: "Yesterday"),
UploadHistoryItem(name: "rfba_wrong.csv", status: "Error", time: "2 days ago"),
];
}
}

View File

@@ -0,0 +1,88 @@
import 'package:pshared/models/recipient/recipient.dart';
import 'package:pshared/models/payment/methods/card.dart';
import 'package:pshared/models/payment/methods/iban.dart';
import 'package:pshared/models/payment/methods/russian_bank.dart';
import 'package:pshared/models/payment/methods/wallet.dart';
import 'package:pshared/models/recipient/status.dart';
import 'package:pshared/models/recipient/type.dart';
class RecipientService {
Future<List<Recipient>> fetchRecipients() async {
await Future.delayed(const Duration(milliseconds: 500));
return RecipientMockData.all;
}
}
class RecipientMockData {
static List<Recipient> get all => [
Recipient.mock(
name: 'Alice Johnson',
email: 'alice@example.com',
status: RecipientStatus.ready,
type: RecipientType.internal,
card: CardPaymentMethod(
pan: '1213',
firstName: 'Alice',
lastName: 'Johnson',
),
),
Recipient.mock(
name: 'Bob & Co Ltd.',
email: 'payout@bobco.com',
status: RecipientStatus.registered,
type: RecipientType.external,
card: CardPaymentMethod(
pan: '4343',
firstName: 'Bob',
lastName: 'Co',
),
iban: IbanPaymentMethod(
iban: 'FR7630***890189',
accountHolder: 'Bob & Co Ltd.',
bic: 'AGRIFRPP',
bankName: 'Credit Agricole',
),
wallet: WalletPaymentMethod(walletId: '8932231'),
),
Recipient.mock(
name: 'Carlos Kline',
email: 'carlos@acme.org',
status: RecipientStatus.notRegistered,
type: RecipientType.internal,
wallet: WalletPaymentMethod(walletId: '7723490'),
),
Recipient.mock(
name: 'Delta Outsourcing GmbH',
email: 'finance@delta-os.de',
status: RecipientStatus.registered,
type: RecipientType.external,
card: CardPaymentMethod(
pan: '9988',
firstName: 'Delta',
lastName: 'GmbH',
),
iban: IbanPaymentMethod(
iban: 'DE4450***324931',
accountHolder: 'Delta Outsourcing GmbH',
bic: 'INGDDEFFXXX',
bankName: 'ING',
),
),
Recipient.mock(
name: 'Erin Patel',
email: 'erin@labster.io',
status: RecipientStatus.ready,
type: RecipientType.internal,
bank: RussianBankAccountPaymentMethod(
accountNumber: '4081***7654',
recipientName: 'Erin Patel',
inn: '7812012345',
kpp: '781201001',
bankName: 'Alfa-Bank',
bik: '044525593',
correspondentAccount: '30101810200000000593',
),
),
];
}

View File

@@ -0,0 +1,41 @@
import 'package:pweb/models/currency.dart';
import 'package:pweb/models/wallet.dart';
abstract class WalletsService {
Future<List<Wallet>> getWallets();
Future<List<Wallet>> updateWallet();
Future<List<Wallet>> createWallet();
Future<List<Wallet>> deleteWallet();
Future<Wallet> getWallet(String walletRef);
}
class MockWalletsService implements WalletsService {
final List<Wallet> _wallets = [
Wallet(id: '1124', walletUserID: 'WA-12345667', name: 'Main Wallet', balance: 10000000.0, currency: Currency.rub),
Wallet(id: '2124', walletUserID: 'WA-76654321', name: 'Savings', balance: 2500.5, currency: Currency.usd),
];
@override
Future<List<Wallet>> getWallets() async {
return _wallets;
}
@override
Future<Wallet> getWallet(String walletId) async {
return _wallets.firstWhere(
(wallet) => wallet.id == walletId,
orElse: () => throw Exception('Wallet not found'),
);
}
@override
Future<List<Wallet>> updateWallet() async => [];
@override
Future<List<Wallet>> createWallet() async => [];
@override
Future<List<Wallet>> deleteWallet() async => [];
}