PostHog last fixes hopefully #109
@@ -11,6 +11,8 @@ class CommonConstants {
|
|||||||
static String apiEndpoint = '/api/v1';
|
static String apiEndpoint = '/api/v1';
|
||||||
static String amplitudeSecret = 'c3d75b3e2520d708440acbb16b923e79';
|
static String amplitudeSecret = 'c3d75b3e2520d708440acbb16b923e79';
|
||||||
static String amplitudeServerZone = 'EU';
|
static String amplitudeServerZone = 'EU';
|
||||||
|
static String posthogApiKey = 'phc_lVhbruaZpxiQxppHBJpL36ARnPlkqbCewv6cauoceTN';
|
||||||
|
static String posthogHost = 'https://eu.i.posthog.com';
|
||||||
static Locale defaultLocale = const Locale('en');
|
static Locale defaultLocale = const Locale('en');
|
||||||
static String defaultCurrency = 'EUR';
|
static String defaultCurrency = 'EUR';
|
||||||
static int defaultDimensionLength = 500;
|
static int defaultDimensionLength = 500;
|
||||||
@@ -36,6 +38,8 @@ class CommonConstants {
|
|||||||
apiEndpoint = configJson['apiEndpoint'] ?? apiEndpoint;
|
apiEndpoint = configJson['apiEndpoint'] ?? apiEndpoint;
|
||||||
amplitudeSecret = configJson['amplitudeSecret'] ?? amplitudeSecret;
|
amplitudeSecret = configJson['amplitudeSecret'] ?? amplitudeSecret;
|
||||||
amplitudeServerZone = configJson['amplitudeServerZone'] ?? amplitudeServerZone;
|
amplitudeServerZone = configJson['amplitudeServerZone'] ?? amplitudeServerZone;
|
||||||
|
posthogApiKey = configJson['posthogApiKey'] ?? posthogApiKey;
|
||||||
|
posthogHost = configJson['posthogHost'] ?? posthogHost;
|
||||||
defaultLocale = Locale(configJson['defaultLocale'] ?? defaultLocale.languageCode);
|
defaultLocale = Locale(configJson['defaultLocale'] ?? defaultLocale.languageCode);
|
||||||
defaultCurrency = configJson['defaultCurrency'] ?? defaultCurrency;
|
defaultCurrency = configJson['defaultCurrency'] ?? defaultCurrency;
|
||||||
wsProto = configJson['wsProto'] ?? wsProto;
|
wsProto = configJson['wsProto'] ?? wsProto;
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ class Constants extends CommonConstants {
|
|||||||
static String get currentOrgKey => CommonConstants.currentOrgKey;
|
static String get currentOrgKey => CommonConstants.currentOrgKey;
|
||||||
static String get apiUrl => CommonConstants.apiUrl;
|
static String get apiUrl => CommonConstants.apiUrl;
|
||||||
static String get serviceUrl => CommonConstants.serviceUrl;
|
static String get serviceUrl => CommonConstants.serviceUrl;
|
||||||
|
static String get posthogApiKey => CommonConstants.posthogApiKey;
|
||||||
|
static String get posthogHost => CommonConstants.posthogHost;
|
||||||
static int get defaultDimensionLength => CommonConstants.defaultDimensionLength;
|
static int get defaultDimensionLength => CommonConstants.defaultDimensionLength;
|
||||||
static String get deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
|
static String get deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
|
||||||
static String get nilObjectRef => CommonConstants.nilObjectRef;
|
static String get nilObjectRef => CommonConstants.nilObjectRef;
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ extension AppConfigExtension on AppConfig {
|
|||||||
external String? get apiEndpoint;
|
external String? get apiEndpoint;
|
||||||
external String? get amplitudeSecret;
|
external String? get amplitudeSecret;
|
||||||
external String? get amplitudeServerZone;
|
external String? get amplitudeServerZone;
|
||||||
|
external String? get posthogApiKey;
|
||||||
|
external String? get posthogHost;
|
||||||
external String? get defaultLocale;
|
external String? get defaultLocale;
|
||||||
external String? get wsProto;
|
external String? get wsProto;
|
||||||
external String? get wsEndpoint;
|
external String? get wsEndpoint;
|
||||||
@@ -40,6 +42,8 @@ class Constants extends CommonConstants {
|
|||||||
static String get currentOrgKey => CommonConstants.currentOrgKey;
|
static String get currentOrgKey => CommonConstants.currentOrgKey;
|
||||||
static String get apiUrl => CommonConstants.apiUrl;
|
static String get apiUrl => CommonConstants.apiUrl;
|
||||||
static String get serviceUrl => CommonConstants.serviceUrl;
|
static String get serviceUrl => CommonConstants.serviceUrl;
|
||||||
|
static String get posthogApiKey => CommonConstants.posthogApiKey;
|
||||||
|
static String get posthogHost => CommonConstants.posthogHost;
|
||||||
static int get defaultDimensionLength => CommonConstants.defaultDimensionLength;
|
static int get defaultDimensionLength => CommonConstants.defaultDimensionLength;
|
||||||
static String get deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
|
static String get deviceIdStorageKey => CommonConstants.deviceIdStorageKey;
|
||||||
static String get nilObjectRef => CommonConstants.nilObjectRef;
|
static String get nilObjectRef => CommonConstants.nilObjectRef;
|
||||||
@@ -57,6 +61,8 @@ class Constants extends CommonConstants {
|
|||||||
'apiEndpoint': config.apiEndpoint,
|
'apiEndpoint': config.apiEndpoint,
|
||||||
'amplitudeSecret': config.amplitudeSecret,
|
'amplitudeSecret': config.amplitudeSecret,
|
||||||
'amplitudeServerZone': config.amplitudeServerZone,
|
'amplitudeServerZone': config.amplitudeServerZone,
|
||||||
|
'posthogApiKey': config.posthogApiKey,
|
||||||
|
'posthogHost': config.posthogHost,
|
||||||
'defaultLocale': config.defaultLocale,
|
'defaultLocale': config.defaultLocale,
|
||||||
'wsProto': config.wsProto,
|
'wsProto': config.wsProto,
|
||||||
'wsEndpoint': config.wsEndpoint,
|
'wsEndpoint': config.wsEndpoint,
|
||||||
|
|||||||
7
frontend/pshared/lib/models/auth/state.dart
Normal file
7
frontend/pshared/lib/models/auth/state.dart
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
enum AuthState {
|
||||||
|
idle,
|
||||||
|
checking,
|
||||||
|
ready,
|
||||||
|
empty,
|
||||||
|
error,
|
||||||
|
}
|
||||||
@@ -79,12 +79,13 @@ enum ResourceType {
|
|||||||
@JsonValue('payments')
|
@JsonValue('payments')
|
||||||
payments,
|
payments,
|
||||||
|
|
||||||
@JsonValue('payment_methods')
|
/// Represents payment orchestration service
|
||||||
paymentMethods,
|
|
||||||
|
|
||||||
@JsonValue('payment_orchestrator')
|
@JsonValue('payment_orchestrator')
|
||||||
paymentOrchestrator,
|
paymentOrchestrator,
|
||||||
|
|
||||||
|
@JsonValue('payment_methods')
|
||||||
|
paymentMethods,
|
||||||
|
|
||||||
/// Represents permissions service
|
/// Represents permissions service
|
||||||
@JsonValue('permissions')
|
@JsonValue('permissions')
|
||||||
permissions,
|
permissions,
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:pshared/models/auth/state.dart';
|
||||||
|
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
|
||||||
@@ -20,8 +23,14 @@ import 'package:pshared/utils/exception.dart';
|
|||||||
|
|
||||||
|
|
||||||
class AccountProvider extends ChangeNotifier {
|
class AccountProvider extends ChangeNotifier {
|
||||||
|
AccountProvider();
|
||||||
|
|
||||||
static String get currentUserRef => Constants.nilObjectRef;
|
static String get currentUserRef => Constants.nilObjectRef;
|
||||||
|
|
||||||
|
/// Auth lifecycle state to avoid multiple ad-hoc flags.
|
||||||
|
AuthState _authState = AuthState.idle;
|
||||||
|
AuthState get authState => _authState;
|
||||||
|
|
||||||
// The resource now wraps our Account? state along with its loading/error state.
|
// The resource now wraps our Account? state along with its loading/error state.
|
||||||
Resource<Account?> _resource = Resource(data: null);
|
Resource<Account?> _resource = Resource(data: null);
|
||||||
Resource<Account?> get resource => _resource;
|
Resource<Account?> get resource => _resource;
|
||||||
@@ -52,9 +61,18 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private helper to update the resource and notify listeners.
|
@protected
|
||||||
|
Future<void> onAccountChanged(Account? previous, Account? current) => Future<void>.value();
|
||||||
|
|
||||||
void _setResource(Resource<Account?> newResource) {
|
void _setResource(Resource<Account?> newResource) {
|
||||||
|
final previousAccount = _resource.data;
|
||||||
_resource = newResource;
|
_resource = newResource;
|
||||||
|
final currentAccount = newResource.data;
|
||||||
|
|
||||||
|
if (previousAccount != currentAccount) {
|
||||||
|
unawaited(onAccountChanged(previousAccount, currentAccount));
|
||||||
|
}
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,6 +93,7 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
locale: locale,
|
locale: locale,
|
||||||
));
|
));
|
||||||
if (outcome.account != null) {
|
if (outcome.account != null) {
|
||||||
|
_authState = AuthState.ready;
|
||||||
_setResource(Resource(data: outcome.account, isLoading: false));
|
_setResource(Resource(data: outcome.account, isLoading: false));
|
||||||
_pickupLocale(outcome.account!.locale);
|
_pickupLocale(outcome.account!.locale);
|
||||||
} else {
|
} else {
|
||||||
@@ -84,10 +103,12 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
await VerificationService.requestLoginCode(pending);
|
await VerificationService.requestLoginCode(pending);
|
||||||
_pendingLogin = pending;
|
_pendingLogin = pending;
|
||||||
|
_authState = AuthState.idle;
|
||||||
_setResource(_resource.copyWith(isLoading: false));
|
_setResource(_resource.copyWith(isLoading: false));
|
||||||
}
|
}
|
||||||
return outcome;
|
return outcome;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
_authState = AuthState.error;
|
||||||
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
@@ -95,6 +116,7 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
void completePendingLogin(Account account) {
|
void completePendingLogin(Account account) {
|
||||||
_pendingLogin = null;
|
_pendingLogin = null;
|
||||||
|
_authState = AuthState.ready;
|
||||||
_setResource(Resource(data: account, isLoading: false, error: null));
|
_setResource(Resource(data: account, isLoading: false, error: null));
|
||||||
_pickupLocale(account.locale);
|
_pickupLocale(account.locale);
|
||||||
}
|
}
|
||||||
@@ -102,13 +124,17 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
Future<bool> isAuthorizationStored() async => AuthorizationService.isAuthorizationStored();
|
Future<bool> isAuthorizationStored() async => AuthorizationService.isAuthorizationStored();
|
||||||
|
|
||||||
Future<Account?> restore() async {
|
Future<Account?> restore() async {
|
||||||
|
_authState = AuthState.checking;
|
||||||
|
notifyListeners();
|
||||||
_setResource(_resource.copyWith(isLoading: true, error: null));
|
_setResource(_resource.copyWith(isLoading: true, error: null));
|
||||||
try {
|
try {
|
||||||
final acc = await AccountService.restore();
|
final acc = await AccountService.restore();
|
||||||
|
_authState = AuthState.ready;
|
||||||
_setResource(Resource(data: acc, isLoading: false));
|
_setResource(Resource(data: acc, isLoading: false));
|
||||||
_pickupLocale(acc.locale);
|
_pickupLocale(acc.locale);
|
||||||
return acc;
|
return acc;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
_authState = AuthState.error;
|
||||||
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
@@ -140,11 +166,14 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> logout() async {
|
Future<void> logout() async {
|
||||||
|
_authState = AuthState.empty;
|
||||||
_setResource(_resource.copyWith(isLoading: true, error: null));
|
_setResource(_resource.copyWith(isLoading: true, error: null));
|
||||||
|
_pendingLogin = null;
|
||||||
try {
|
try {
|
||||||
await AccountService.logout();
|
await AccountService.logout();
|
||||||
_setResource(Resource(data: null, isLoading: false));
|
_setResource(Resource(data: null, isLoading: false));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
_authState = AuthState.error;
|
||||||
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
_setResource(_resource.copyWith(isLoading: false, error: toException(e)));
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
@@ -220,4 +249,15 @@ class AccountProvider extends ChangeNotifier {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> restoreIfPossible() async {
|
||||||
|
if (_authState == AuthState.checking || _authState == AuthState.ready) return;
|
||||||
|
final hasAuth = await AuthorizationService.isAuthorizationStored();
|
||||||
|
if (!hasAuth) {
|
||||||
|
_authState = AuthState.empty;
|
||||||
|
notifyListeners();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await restore();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,4 +80,12 @@ class OrganizationsProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> reset() async {
|
||||||
|
_resource = Resource(data: []);
|
||||||
|
_currentOrg = null;
|
||||||
|
notifyListeners();
|
||||||
|
// Best-effort cleanup of stored selection to avoid using stale org on next login.
|
||||||
|
await SecureStorageService.delete(Constants.currentOrgKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@@ -21,9 +23,17 @@ class PermissionsProvider extends ChangeNotifier {
|
|||||||
Resource<UserAccess> _userAccess = Resource(data: null, isLoading: false, error: null);
|
Resource<UserAccess> _userAccess = Resource(data: null, isLoading: false, error: null);
|
||||||
late OrganizationsProvider _organizations;
|
late OrganizationsProvider _organizations;
|
||||||
bool _isLoaded = false;
|
bool _isLoaded = false;
|
||||||
|
String? _loadedOrgRef;
|
||||||
|
//For permissions to auto-load when an organization is set, so the dashboard no longer hangs waiting for permissions to become ready.
|
||||||
|
|
||||||
void update(OrganizationsProvider venue) {
|
void update(OrganizationsProvider venue) {
|
||||||
_organizations = venue;
|
_organizations = venue;
|
||||||
|
// Trigger a reload when organization changes or when permissions were never loaded.
|
||||||
|
if (_organizations.isOrganizationSet &&
|
||||||
|
_loadedOrgRef != _organizations.current.id &&
|
||||||
|
!_userAccess.isLoading) {
|
||||||
|
unawaited(load());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generic wrapper to perform service calls and reload state
|
// Generic wrapper to perform service calls and reload state
|
||||||
@@ -43,6 +53,10 @@ class PermissionsProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
/// Load the [UserAccess] for the current venue.
|
/// Load the [UserAccess] for the current venue.
|
||||||
Future<UserAccess?> load() async {
|
Future<UserAccess?> load() async {
|
||||||
|
if (!_organizations.isOrganizationSet) {
|
||||||
|
// Organization is not ready yet; skip loading until it becomes available.
|
||||||
|
return _userAccess.data;
|
||||||
|
}
|
||||||
_userAccess = _userAccess.copyWith(isLoading: true, error: null);
|
_userAccess = _userAccess.copyWith(isLoading: true, error: null);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
@@ -56,6 +70,7 @@ class PermissionsProvider extends ChangeNotifier {
|
|||||||
_userAccess = _userAccess.copyWith(data: allAccess, isLoading: false);
|
_userAccess = _userAccess.copyWith(data: allAccess, isLoading: false);
|
||||||
}
|
}
|
||||||
_isLoaded = true;
|
_isLoaded = true;
|
||||||
|
_loadedOrgRef = orgRef;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_userAccess = _userAccess.copyWith(
|
_userAccess = _userAccess.copyWith(
|
||||||
error: e is Exception ? e : Exception(e.toString()),
|
error: e is Exception ? e : Exception(e.toString()),
|
||||||
@@ -164,9 +179,14 @@ class PermissionsProvider extends ChangeNotifier {
|
|||||||
void reset() {
|
void reset() {
|
||||||
_userAccess = Resource(data: null, isLoading: false, error: null);
|
_userAccess = Resource(data: null, isLoading: false, error: null);
|
||||||
_isLoaded = false;
|
_isLoaded = false;
|
||||||
|
_loadedOrgRef = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> resetAsync() async {
|
||||||
|
reset();
|
||||||
|
}
|
||||||
|
|
||||||
bool canRead(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.read, objectRef: objectRef);
|
bool canRead(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.read, objectRef: objectRef);
|
||||||
bool canUpdate(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.update, objectRef: objectRef);
|
bool canUpdate(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.update, objectRef: objectRef);
|
||||||
bool canDelete(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.delete, objectRef: objectRef);
|
bool canDelete(ResourceType r, {Object? objectRef}) => canAccessResource(r, action: perm.Action.delete, objectRef: objectRef);
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import 'package:pshared/provider/locale.dart';
|
|||||||
import 'package:pshared/provider/permissions.dart';
|
import 'package:pshared/provider/permissions.dart';
|
||||||
import 'package:pshared/provider/account.dart';
|
import 'package:pshared/provider/account.dart';
|
||||||
import 'package:pshared/provider/organizations.dart';
|
import 'package:pshared/provider/organizations.dart';
|
||||||
|
import 'package:pshared/provider/payment/amount.dart';
|
||||||
|
import 'package:pshared/provider/payment/quotation.dart';
|
||||||
import 'package:pshared/provider/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
import 'package:pshared/provider/recipient/pmethods.dart';
|
import 'package:pshared/provider/recipient/pmethods.dart';
|
||||||
|
|
||||||
@@ -26,8 +28,10 @@ import 'package:pweb/providers/wallets.dart';
|
|||||||
import 'package:pweb/providers/wallet_transactions.dart';
|
import 'package:pweb/providers/wallet_transactions.dart';
|
||||||
import 'package:pweb/services/operations.dart';
|
import 'package:pweb/services/operations.dart';
|
||||||
import 'package:pweb/services/payments/history.dart';
|
import 'package:pweb/services/payments/history.dart';
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
import 'package:pweb/services/wallet_transactions.dart';
|
import 'package:pweb/services/wallet_transactions.dart';
|
||||||
import 'package:pweb/services/wallets.dart';
|
import 'package:pweb/services/wallets.dart';
|
||||||
|
import 'package:pweb/providers/account.dart';
|
||||||
|
|
||||||
|
|
||||||
void _setupLogging() {
|
void _setupLogging() {
|
||||||
@@ -39,11 +43,9 @@ void _setupLogging() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
await Constants.initialize();
|
|
||||||
|
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
await Constants.initialize();
|
||||||
// await AmplitudeService.initialize();
|
await PosthogService.initialize();
|
||||||
|
|
||||||
|
|
||||||
_setupLogging();
|
_setupLogging();
|
||||||
@@ -56,7 +58,7 @@ void main() async {
|
|||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (_) => LocaleProvider(null)),
|
ChangeNotifierProvider(create: (_) => LocaleProvider(null)),
|
||||||
ChangeNotifierProxyProvider<LocaleProvider, AccountProvider>(
|
ChangeNotifierProxyProvider<LocaleProvider, AccountProvider>(
|
||||||
create: (_) => AccountProvider(),
|
create: (_) => PwebAccountProvider(),
|
||||||
update: (context, localeProvider, provider) => provider!..updateProvider(localeProvider),
|
update: (context, localeProvider, provider) => provider!..updateProvider(localeProvider),
|
||||||
),
|
),
|
||||||
ChangeNotifierProxyProvider<AccountProvider, TwoFactorProvider>(
|
ChangeNotifierProxyProvider<AccountProvider, TwoFactorProvider>(
|
||||||
@@ -69,6 +71,7 @@ void main() async {
|
|||||||
update: (context, orgnization, provider) => provider!..update(orgnization),
|
update: (context, orgnization, provider) => provider!..update(orgnization),
|
||||||
),
|
),
|
||||||
ChangeNotifierProvider(create: (_) => CarouselIndexProvider()),
|
ChangeNotifierProvider(create: (_) => CarouselIndexProvider()),
|
||||||
|
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => UploadHistoryProvider(service: MockUploadHistoryService())..load(),
|
create: (_) => UploadHistoryProvider(service: MockUploadHistoryService())..load(),
|
||||||
),
|
),
|
||||||
@@ -90,9 +93,17 @@ void main() async {
|
|||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => MockPaymentProvider(),
|
create: (_) => MockPaymentProvider(),
|
||||||
),
|
),
|
||||||
|
|
||||||
ChangeNotifierProvider(
|
ChangeNotifierProvider(
|
||||||
create: (_) => OperationProvider(OperationService())..loadOperations(),
|
create: (_) => OperationProvider(OperationService())..loadOperations(),
|
||||||
),
|
),
|
||||||
|
ChangeNotifierProvider(
|
||||||
|
create: (_) => PaymentAmountProvider(),
|
||||||
|
),
|
||||||
|
ChangeNotifierProxyProvider2<OrganizationsProvider, PaymentAmountProvider, QuotationProvider>(
|
||||||
|
create: (_) => QuotationProvider(),
|
||||||
|
update: (context, orgnization, payment, provider) => provider!..update(orgnization, payment),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
child: const PayApp(),
|
child: const PayApp(),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
@@ -14,6 +16,7 @@ import 'package:pshared/provider/recipient/pmethods.dart';
|
|||||||
import 'package:pshared/provider/recipient/provider.dart';
|
import 'package:pshared/provider/recipient/provider.dart';
|
||||||
|
|
||||||
import 'package:pweb/pages/address_book/form/view.dart';
|
import 'package:pweb/pages/address_book/form/view.dart';
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
import 'package:pweb/utils/error/snackbar.dart';
|
import 'package:pweb/utils/error/snackbar.dart';
|
||||||
import 'package:pweb/utils/payment/label.dart';
|
import 'package:pweb/utils/payment/label.dart';
|
||||||
import 'package:pweb/utils/snackbar.dart';
|
import 'package:pweb/utils/snackbar.dart';
|
||||||
@@ -106,11 +109,11 @@ class _AdressBookRecipientFormState extends State<AdressBookRecipientForm> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// AmplitudeService.recipientAddCompleted(
|
unawaited(PosthogService.recipientAddCompleted(
|
||||||
// _type,
|
_type,
|
||||||
// _status,
|
_status,
|
||||||
// _methods.keys.toSet(),
|
_methods.keys.toSet(),
|
||||||
// );
|
));
|
||||||
final recipient = await executeActionWithNotification(
|
final recipient = await executeActionWithNotification(
|
||||||
context: context,
|
context: context,
|
||||||
action: _doSave,
|
action: _doSave,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/models/auth/state.dart';
|
||||||
import 'package:pshared/provider/account.dart';
|
import 'package:pshared/provider/account.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/pages.dart';
|
import 'package:pweb/app/router/pages.dart';
|
||||||
@@ -10,26 +11,64 @@ import 'package:pweb/widgets/error/snackbar.dart';
|
|||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class AccountLoader extends StatelessWidget {
|
class AccountLoader extends StatefulWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const AccountLoader({super.key, required this.child});
|
const AccountLoader({super.key, required this.child});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Consumer<AccountProvider>(builder: (context, provider, _) {
|
State<AccountLoader> createState() => _AccountLoaderState();
|
||||||
if (provider.isLoading) return const Center(child: CircularProgressIndicator());
|
}
|
||||||
if (provider.error != null) {
|
|
||||||
|
class _AccountLoaderState extends State<AccountLoader> {
|
||||||
|
AuthState? _handledState;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) return;
|
||||||
|
Provider.of<AccountProvider>(context, listen: false).restoreIfPossible();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleSideEffects(AccountProvider provider) {
|
||||||
|
if (_handledState == provider.authState) return;
|
||||||
|
_handledState = provider.authState;
|
||||||
|
|
||||||
|
void goToLogin() {
|
||||||
|
if (!mounted) return;
|
||||||
|
navigateAndReplace(context, Pages.login);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (provider.authState) {
|
||||||
|
case AuthState.error:
|
||||||
|
final error = provider.error ?? Exception('Authorization failed');
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!mounted) return;
|
||||||
postNotifyUserOfErrorX(
|
postNotifyUserOfErrorX(
|
||||||
context: context,
|
context: context,
|
||||||
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
||||||
exception: provider.error!,
|
exception: error,
|
||||||
);
|
);
|
||||||
navigateAndReplace(context, Pages.login);
|
goToLogin();
|
||||||
}
|
|
||||||
if (provider.account == null) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => navigateAndReplace(context, Pages.login));
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
return child;
|
|
||||||
});
|
});
|
||||||
|
break;
|
||||||
|
case AuthState.empty:
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) => goToLogin());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<AccountProvider>(builder: (context, provider, _) {
|
||||||
|
_handleSideEffects(provider);
|
||||||
|
if (provider.authState == AuthState.ready && provider.account != null) {
|
||||||
|
return widget.child;
|
||||||
|
}
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ import 'package:provider/provider.dart';
|
|||||||
|
|
||||||
import 'package:pshared/provider/organizations.dart';
|
import 'package:pshared/provider/organizations.dart';
|
||||||
|
|
||||||
import 'package:pweb/app/router/pages.dart';
|
|
||||||
import 'package:pweb/widgets/error/snackbar.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -19,12 +16,20 @@ class OrganizationLoader extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) => Consumer<OrganizationsProvider>(builder: (context, provider, _) {
|
Widget build(BuildContext context) => Consumer<OrganizationsProvider>(builder: (context, provider, _) {
|
||||||
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(
|
final loc = AppLocalizations.of(context)!;
|
||||||
context: context,
|
return Center(
|
||||||
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
child: Column(
|
||||||
exception: provider.error!,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(loc.errorLogin),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: provider.load,
|
||||||
|
child: Text(loc.retry),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
navigateAndReplace(context, Pages.login);
|
|
||||||
}
|
}
|
||||||
if ((provider.error == null) && (!provider.isOrganizationSet)) {
|
if ((provider.error == null) && (!provider.isOrganizationSet)) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
|||||||
@@ -5,36 +5,49 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:pshared/provider/account.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/widgets/error/snackbar.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
|
|
||||||
class PermissionsLoader extends StatelessWidget {
|
class PermissionsLoader extends StatelessWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
|
||||||
const PermissionsLoader({super.key, required this.child});
|
const PermissionsLoader({super.key, required this.child});
|
||||||
|
|
||||||
@override
|
void _triggerLoadIfNeeded(PermissionsProvider provider) {
|
||||||
Widget build(BuildContext context) => Consumer2<PermissionsProvider, AccountProvider>(builder: (context, provider, accountProvider, _) {
|
if (!provider.isLoading && !provider.isReady) {
|
||||||
if (provider.isLoading) {
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
if (provider.error != null) {
|
|
||||||
postNotifyUserOfErrorX(
|
|
||||||
context: context,
|
|
||||||
errorSituation: AppLocalizations.of(context)!.errorLogin,
|
|
||||||
exception: provider.error!,
|
|
||||||
);
|
|
||||||
navigateAndReplace(context, Pages.login);
|
|
||||||
}
|
|
||||||
if (provider.error == null && !provider.isReady && accountProvider.account != null) {
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!provider.isLoading && !provider.isReady) {
|
||||||
provider.load();
|
provider.load();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer2<PermissionsProvider, AccountProvider>(
|
||||||
|
builder: (context, provider, _accountProvider, _) {
|
||||||
|
if (provider.error != null) {
|
||||||
|
final loc = AppLocalizations.of(context)!;
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(loc.errorLogin),
|
||||||
|
const SizedBox(height: 12),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: provider.load,
|
||||||
|
child: Text(loc.retry),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_triggerLoadIfNeeded(provider);
|
||||||
|
if (provider.isLoading || !provider.isReady) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
return child;
|
return child;
|
||||||
});
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
@@ -14,6 +16,7 @@ import 'package:pweb/widgets/password/password.dart';
|
|||||||
import 'package:pweb/widgets/username.dart';
|
import 'package:pweb/widgets/username.dart';
|
||||||
import 'package:pweb/widgets/vspacer.dart';
|
import 'package:pweb/widgets/vspacer.dart';
|
||||||
import 'package:pweb/widgets/error/snackbar.dart';
|
import 'package:pweb/widgets/error/snackbar.dart';
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
@@ -43,6 +46,7 @@ class _LoginFormState extends State<LoginForm> {
|
|||||||
password: _passwordController.text,
|
password: _passwordController.text,
|
||||||
locale: context.read<LocaleProvider>().locale.languageCode,
|
locale: context.read<LocaleProvider>().locale.languageCode,
|
||||||
);
|
);
|
||||||
|
unawaited(PosthogService.login(pending: outcome.isPending));
|
||||||
if (outcome.isPending) {
|
if (outcome.isPending) {
|
||||||
// TODO: fix context usage
|
// TODO: fix context usage
|
||||||
navigateAndReplace(context, Pages.sfactor);
|
navigateAndReplace(context, Pages.sfactor);
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import 'package:pweb/providers/payment_flow.dart';
|
|||||||
import 'package:pweb/pages/payment_methods/payment_page/body.dart';
|
import 'package:pweb/pages/payment_methods/payment_page/body.dart';
|
||||||
import 'package:pweb/providers/wallets.dart';
|
import 'package:pweb/providers/wallets.dart';
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
|
|
||||||
|
|
||||||
class PaymentPage extends StatefulWidget {
|
class PaymentPage extends StatefulWidget {
|
||||||
@@ -109,7 +110,7 @@ class _PaymentPageState extends State<PaymentPage> {
|
|||||||
|
|
||||||
void _handleSendPayment() {
|
void _handleSendPayment() {
|
||||||
// TODO: Handle Payment logic
|
// TODO: Handle Payment logic
|
||||||
// AmplitudeService.paymentInitiated();
|
PosthogService.paymentInitiated(method: _flowProvider.selectedType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/provider/locale.dart';
|
import 'package:pshared/provider/locale.dart';
|
||||||
|
|
||||||
// import 'package:pweb/services/amplitude.dart';
|
import 'package:pweb/services/posthog.dart';
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
@@ -58,7 +60,7 @@ class LocalePicker extends StatelessWidget {
|
|||||||
onChanged: (locale) {
|
onChanged: (locale) {
|
||||||
if (locale != null) {
|
if (locale != null) {
|
||||||
localeProvider.setLocale(locale);
|
localeProvider.setLocale(locale);
|
||||||
// AmplitudeService.localeChanged(locale);
|
unawaited(PosthogService.localeChanged(locale));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
|
|||||||
17
frontend/pweb/lib/providers/account.dart
Normal file
17
frontend/pweb/lib/providers/account.dart
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:pshared/models/account/account.dart';
|
||||||
|
import 'package:pshared/provider/account.dart';
|
||||||
|
|
||||||
|
import 'package:pweb/services/posthog.dart';
|
||||||
|
|
||||||
|
|
||||||
|
class PwebAccountProvider extends AccountProvider {
|
||||||
|
@override
|
||||||
|
Future<void> onAccountChanged(Account? previous, Account? current) {
|
||||||
|
if (current != null) {
|
||||||
|
return PosthogService.identify(current);
|
||||||
|
}
|
||||||
|
return PosthogService.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@ class TwoFactorProvider extends ChangeNotifier {
|
|||||||
bool _hasError = false;
|
bool _hasError = false;
|
||||||
bool _verificationSuccess = false;
|
bool _verificationSuccess = false;
|
||||||
String? _errorMessage;
|
String? _errorMessage;
|
||||||
|
String? _currentPendingToken;
|
||||||
|
|
||||||
bool get isSubmitting => _isSubmitting;
|
bool get isSubmitting => _isSubmitting;
|
||||||
bool get hasError => _hasError;
|
bool get hasError => _hasError;
|
||||||
@@ -26,6 +27,12 @@ class TwoFactorProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
void update(AccountProvider accountProvider) {
|
void update(AccountProvider accountProvider) {
|
||||||
_accountProvider = accountProvider;
|
_accountProvider = accountProvider;
|
||||||
|
final pending = accountProvider.pendingLogin;
|
||||||
|
final token = pending?.pendingToken.token;
|
||||||
|
if (token != _currentPendingToken || accountProvider.account == null) {
|
||||||
|
_resetState();
|
||||||
|
_currentPendingToken = token;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> submitCode(String code) async {
|
Future<void> submitCode(String code) async {
|
||||||
@@ -46,6 +53,7 @@ class TwoFactorProvider extends ChangeNotifier {
|
|||||||
);
|
);
|
||||||
_accountProvider.completePendingLogin(account);
|
_accountProvider.completePendingLogin(account);
|
||||||
_verificationSuccess = true;
|
_verificationSuccess = true;
|
||||||
|
_currentPendingToken = null;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_hasError = true;
|
_hasError = true;
|
||||||
_errorMessage = e.toString();
|
_errorMessage = e.toString();
|
||||||
@@ -71,4 +79,17 @@ class TwoFactorProvider extends ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
_resetState();
|
||||||
|
_currentPendingToken = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _resetState() {
|
||||||
|
_isSubmitting = false;
|
||||||
|
_hasError = false;
|
||||||
|
_errorMessage = null;
|
||||||
|
_verificationSuccess = false;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
136
frontend/pweb/lib/services/posthog.dart
Normal file
136
frontend/pweb/lib/services/posthog.dart
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
|
import 'package:posthog_flutter/posthog_flutter.dart';
|
||||||
|
|
||||||
|
import 'package:pshared/config/constants.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 PosthogService {
|
||||||
|
static final _logger = Logger('service.posthog');
|
||||||
|
static String? _identifiedUserId;
|
||||||
|
static bool _initialized = false;
|
||||||
|
|
||||||
|
static bool get isEnabled => _initialized;
|
||||||
|
|
||||||
|
static Future<void> initialize() async {
|
||||||
|
final apiKey = Constants.posthogApiKey;
|
||||||
|
if (apiKey.isEmpty) {
|
||||||
|
_logger.warning('PostHog API key is not configured, analytics disabled.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final config = PostHogConfig(apiKey)
|
||||||
|
..host = Constants.posthogHost
|
||||||
|
..captureApplicationLifecycleEvents = true;
|
||||||
|
await Posthog().setup(config);
|
||||||
|
await Posthog().register('client_id', Constants.clientId);
|
||||||
|
_initialized = true;
|
||||||
|
_logger.info('PostHog initialized with host ${Constants.posthogHost}');
|
||||||
|
} catch (e, st) {
|
||||||
|
_initialized = false;
|
||||||
|
_logger.warning('Failed to initialize PostHog: $e', e, st);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> identify(Account account) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
if (_identifiedUserId == account.id) return;
|
||||||
|
|
||||||
|
await Posthog().identify(
|
||||||
|
userId: account.id,
|
||||||
|
userProperties: {
|
||||||
|
'email': account.login,
|
||||||
|
'name': account.name,
|
||||||
|
'locale': account.locale,
|
||||||
|
'created_at': account.createdAt.toIso8601String(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
_identifiedUserId = account.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> reset() async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
_identifiedUserId = null;
|
||||||
|
await Posthog().reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> login({required bool pending}) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
await _capture(
|
||||||
|
'login',
|
||||||
|
properties: {
|
||||||
|
'result': pending ? 'pending' : 'success',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> pageOpened(PayoutDestination page, {String? path, String? uiSource}) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
return _capture(
|
||||||
|
'pageOpened',
|
||||||
|
properties: {
|
||||||
|
'page': page.name,
|
||||||
|
if (path != null) 'path': path,
|
||||||
|
if (uiSource != null) 'uiSource': uiSource,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> localeChanged(Locale locale) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
return _capture(
|
||||||
|
'localeChanged',
|
||||||
|
properties: {'locale': locale.toLanguageTag()},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> recipientAddCompleted(
|
||||||
|
RecipientType type,
|
||||||
|
RecipientStatus status,
|
||||||
|
Set<PaymentType> methods,
|
||||||
|
) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
return _capture(
|
||||||
|
'recipientAddCompleted',
|
||||||
|
properties: {
|
||||||
|
'methods': methods.map((m) => m.name).toList(),
|
||||||
|
'type': type.name,
|
||||||
|
'status': status.name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> paymentInitiated({PaymentType? method}) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
return _capture(
|
||||||
|
'paymentInitiated',
|
||||||
|
properties: {
|
||||||
|
if (method != null) 'method': method.name,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> _capture(
|
||||||
|
String eventName, {
|
||||||
|
Map<String, Object?>? properties,
|
||||||
|
}) async {
|
||||||
|
if (!_initialized) return;
|
||||||
|
final filtered = <String, Object>{};
|
||||||
|
if (properties != null) {
|
||||||
|
for (final entry in properties.entries) {
|
||||||
|
final value = entry.value;
|
||||||
|
if (value != null) filtered[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Posthog().capture(eventName: eventName, properties: filtered.isEmpty ? null : filtered);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,31 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
import 'package:pshared/provider/account.dart';
|
import 'package:pshared/provider/account.dart';
|
||||||
|
import 'package:pshared/provider/organizations.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';
|
||||||
|
import 'package:pweb/providers/two_factor.dart';
|
||||||
|
|
||||||
|
|
||||||
void logoutUtil(BuildContext context) {
|
Future<void> logoutUtil(BuildContext context) async {
|
||||||
context.read<AccountProvider>().logout();
|
final accountProvider = context.read<AccountProvider>();
|
||||||
context.read<PermissionsProvider>().reset();
|
final permissionsProvider = context.read<PermissionsProvider>();
|
||||||
navigateAndReplace(context, Pages.login);
|
final organizationsProvider = context.read<OrganizationsProvider>();
|
||||||
|
final twoFactorProvider = context.read<TwoFactorProvider>();
|
||||||
|
await accountProvider.logout();
|
||||||
|
permissionsProvider.reset();
|
||||||
|
await organizationsProvider.reset();
|
||||||
|
twoFactorProvider.reset();
|
||||||
|
|
||||||
|
final router = GoRouter.of(context);
|
||||||
|
final loginPath = routerPage(Pages.login);
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
if (!context.mounted) return;
|
||||||
|
router.go(loginPath);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class PayoutAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
final Widget title;
|
final Widget title;
|
||||||
final VoidCallback onAddFundsPressed;
|
final VoidCallback onAddFundsPressed;
|
||||||
final List<Widget>? actions;
|
final List<Widget>? actions;
|
||||||
final VoidCallback? onLogout;
|
final Future<void> Function()? onLogout;
|
||||||
final String? avatarUrl;
|
final String? avatarUrl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ class ProfileAvatar extends StatelessWidget {
|
|||||||
const ProfileAvatar({super.key, this.avatarUrl, this.onLogout});
|
const ProfileAvatar({super.key, this.avatarUrl, this.onLogout});
|
||||||
|
|
||||||
final String? avatarUrl;
|
final String? avatarUrl;
|
||||||
final VoidCallback? onLogout;
|
final Future<void> Function()? onLogout;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => PopupMenuButton<int>(
|
Widget build(BuildContext context) => PopupMenuButton<int>(
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:provider/provider.dart';
|
import 'package:pweb/utils/logout.dart';
|
||||||
|
|
||||||
import 'package:pshared/provider/account.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/app/router/pages.dart';
|
|
||||||
|
|
||||||
import 'package:pweb/generated/i18n/app_localizations.dart';
|
import 'package:pweb/generated/i18n/app_localizations.dart';
|
||||||
|
|
||||||
@@ -23,10 +19,8 @@ class LogoutTile extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _logout(BuildContext context) {
|
Future<void> _logout(BuildContext context) async {
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
final accountProvider = Provider.of<AccountProvider>(context, listen: false);
|
await logoutUtil(context);
|
||||||
accountProvider.logout();
|
|
||||||
navigateAndReplace(context, Pages.login);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
// import 'package:pweb/services/amplitude.dart';
|
import 'package:pweb/services/posthog.dart';
|
||||||
import 'package:pweb/widgets/sidebar/destinations.dart';
|
import 'package:pweb/widgets/sidebar/destinations.dart';
|
||||||
|
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@ class SideMenuColumn extends StatelessWidget {
|
|||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onSelected(item);
|
onSelected(item);
|
||||||
// AmplitudeService.pageOpened(item, uiSource: 'sidebar');
|
unawaited(PosthogService.pageOpened(item, uiSource: 'sidebar'));
|
||||||
},
|
},
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
hoverColor: theme.colorScheme.primaryContainer,
|
hoverColor: theme.colorScheme.primaryContainer,
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import amplitude_flutter
|
|||||||
import file_selector_macos
|
import file_selector_macos
|
||||||
import flutter_timezone
|
import flutter_timezone
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
|
import posthog_flutter
|
||||||
import share_plus
|
import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
@@ -19,6 +20,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
|||||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||||
FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
|
FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
PosthogFlutterPlugin.register(with: registry.registrar(forPlugin: "PosthogFlutterPlugin"))
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
pshared:
|
pshared:
|
||||||
path: ../pshared
|
path: ../pshared
|
||||||
|
posthog_flutter: ^5.9.0
|
||||||
|
|
||||||
# The following adds the Cupertino Icons font to your application.
|
# The following adds the Cupertino Icons font to your application.
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
|
|||||||
Reference in New Issue
Block a user