Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Creator Dashboard and Analytics #1492

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/assets/images/ic_apps.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions app/assets/images/ic_chart2.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions app/assets/images/ic_money.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions app/assets/images/ic_users.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 2 additions & 4 deletions app/lib/backend/http/api/apps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,8 @@ Future<bool> isAppSetupCompleted(String? url) async {
headers: {},
body: '',
);
var data;
try {
data = jsonDecode(response?.body ?? '{}');
print(data);
var data = jsonDecode(response?.body ?? '{}');
return data['is_setup_completed'] ?? false;
} on FormatException catch (e) {
debugPrint('Response not a valid json: $e');
Expand All @@ -160,7 +158,7 @@ Future<bool> isAppSetupCompleted(String? url) async {
Future<bool> submitAppServer(File file, Map<String, dynamic> appData) async {
var request = http.MultipartRequest(
'POST',
Uri.parse('${Env.apiBaseUrl}v1/apps'),
Uri.parse('${Env.apiBaseUrl}v2/apps'),
);
request.files.add(await http.MultipartFile.fromPath('file', file.path, filename: basename(file.path)));
request.headers.addAll({'Authorization': await getAuthHeader()});
Expand Down
107 changes: 107 additions & 0 deletions app/lib/backend/http/api/users.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:friend_private/backend/http/shared.dart';
import 'package:friend_private/backend/schema/geolocation.dart';
import 'package:friend_private/backend/schema/person.dart';
import 'package:friend_private/backend/schema/profile.dart';
import 'package:friend_private/env/env.dart';
import 'package:instabug_flutter/instabug_flutter.dart';

Expand Down Expand Up @@ -280,3 +281,109 @@ Future<bool> getHasConversationSummaryRating(String conversationId) async {
return false;
}
}

Future<CreatorProfile?> getCreatorProfile() async {
try {
var response = await makeApiCall(
url: '${Env.apiBaseUrl}v1/users/creator-profile',
headers: {},
method: 'GET',
body: '',
);
print(response?.body);
if (response == null) return null;
debugPrint('getCreatorProfile response: ${response.body}');
if (response.statusCode == 200) {
Map<String, dynamic> json = jsonDecode(response.body);
if (json.isEmpty) {
return CreatorProfile.empty();
}
return CreatorProfile.fromJson(json);
}
return null;
} catch (e) {
debugPrint('getCreatorProfile error: $e');
return null;
}
}

Future<bool> saveCreatorProfile(CreatorProfile profile) async {
try {
var response = await makeApiCall(
url: '${Env.apiBaseUrl}v1/users/creator-profile',
headers: {},
method: 'POST',
body: jsonEncode(profile.toJson()),
);
if (response == null) return false;
debugPrint('saveCreatorProfile response: ${response.body}');
return response.statusCode == 200;
} catch (e) {
debugPrint('saveCreatorProfile error: $e');
return false;
}
}

Future<bool> updateCreatorProfileServer(String? name, String? email, String? paypalEmail, String? paypalLink) async {
try {
var response = await makeApiCall(
url: '${Env.apiBaseUrl}v1/users/creator-profile',
headers: {},
method: 'PATCH',
body: jsonEncode({
'creator_name': name,
'creator_email': email,
'paypal_details': {
'paypal_email': paypalEmail,
'paypal_me_link': paypalLink,
},
}),
);
if (response == null) return false;
debugPrint('updateCreatorProfile response: ${response.body}');
return response.statusCode == 200;
} catch (e) {
debugPrint('updateCreatorProfile error: $e');
return false;
}
}

Future<CreatorStats?> getCreatorStatsServer() async {
try {
var response = await makeApiCall(
url: '${Env.apiBaseUrl}v1/users/creator-stats',
headers: {},
method: 'GET',
body: '',
);
if (response == null) return null;
debugPrint('getCreatorStatsServer response: ${response.body}');
if (response.statusCode == 200) {
return CreatorStats.fromJson(jsonDecode(response.body));
}
return null;
} catch (e) {
debugPrint('getCreatorStatsServer error: $e');
return null;
}
}

Future getPayoutHistoryServer() async {
try {
var response = await makeApiCall(
url: '${Env.apiBaseUrl}v1/users/payout-history',
headers: {},
method: 'GET',
body: '',
);
if (response == null) return null;
debugPrint('getPayoutHistoryServer response: ${response.body}');
if (response.statusCode == 200) {
return PayoutTransaction.fromJsonList(jsonDecode(response.body));
}
return null;
} catch (e) {
debugPrint('getPayoutHistoryServer error: $e');
return null;
}
}
168 changes: 168 additions & 0 deletions app/lib/backend/schema/profile.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
class PayPalDetails {
final String email;
final String? paypalMeLink;

PayPalDetails({
required this.email,
this.paypalMeLink,
});

factory PayPalDetails.fromJson(Map<String, dynamic> json) {
return PayPalDetails(
email: json['paypal_email'],
paypalMeLink: json['paypal_me_link'] ?? '',
);
}

Map<String, dynamic> toJson() {
return {
'paypal_email': email,
'paypal_me_link': paypalMeLink ?? '',
};
}
}

class CreatorProfile {
final String creatorName;
final String creatorEmail;
final PayPalDetails paypalDetails;
final bool? isVerified;

CreatorProfile({
required this.creatorName,
required this.creatorEmail,
required this.paypalDetails,
this.isVerified,
});

factory CreatorProfile.fromJson(Map<String, dynamic> json) {
return CreatorProfile(
creatorName: json['creator_name'],
creatorEmail: json['creator_email'],
paypalDetails: PayPalDetails.fromJson(json['paypal_details']),
isVerified: json['is_verified'] ?? false,
);
}

Map<String, dynamic> toJson() {
return {
'creator_name': creatorName,
'creator_email': creatorEmail,
'paypal_details': paypalDetails.toJson(),
'is_verified': isVerified ?? false,
};
}

bool isEmpty() {
return creatorName.isEmpty && creatorEmail.isEmpty && paypalDetails.email.isEmpty;
}

static CreatorProfile empty() {
return CreatorProfile(
creatorName: '',
creatorEmail: '',
paypalDetails: PayPalDetails(email: ''),
isVerified: false,
);
}
}

class CreatorStats {
final int usageCount;
final double moneyMade;
final int appsCount;
final int activeUsers;

CreatorStats({
required this.usageCount,
required this.moneyMade,
required this.appsCount,
required this.activeUsers,
});

factory CreatorStats.fromJson(Map<String, dynamic> json) {
var usageCount = json['usage_count'] as Map<String, dynamic>;
var totalUsage = usageCount.values.fold(0, (prev, element) => (prev + element).toInt());

var moneyMade = json['money_made'] as Map<String, dynamic>;
var totalMoneyMade = moneyMade.values.fold(0.0, (prev, element) => (prev + element));
var activeUsers = json['active_users'] as Map<String, dynamic>;
var totalActiveUsers = activeUsers.values.fold(0, (prev, element) => (prev + element).toInt());

return CreatorStats(
usageCount: totalUsage,
moneyMade: totalMoneyMade,
appsCount: json['apps_count'].length,
activeUsers: totalActiveUsers,
);
}
}

class PayoutTransaction {
final String amount;
final String currency;
final DateTime date;
final String paymentStatus;
final String payoutMethod;

PayoutTransaction({
required this.amount,
required this.date,
required this.paymentStatus,
required this.payoutMethod,
required this.currency,
});

factory PayoutTransaction.fromJson(Map<String, dynamic> json) {
return PayoutTransaction(
amount: json['amount']['value'],
currency: json['amount']['currency_code'],
date: DateTime.parse(json['payment_date']).toLocal(),
paymentStatus: json['payment_status'],
payoutMethod: json['payee']['payment_method'],
);
}

PayoutTransaction.empty()
: amount = '',
currency = '',
date = DateTime.now(),
paymentStatus = '',
payoutMethod = '';

bool isPending() {
return paymentStatus == 'pending';
}

bool isSuccessful() {
return paymentStatus == 'successful';
}

bool isFailed() {
return paymentStatus == 'failed';
}

String paymentStatusText() {
if (isPending()) {
return 'Pending';
} else if (isSuccessful()) {
return 'Successful';
} else if (isFailed()) {
return 'Failed';
} else {
return 'Unknown';
}
}

String payoutMethodText() {
if (payoutMethod == 'paypal') {
return 'PayPal';
} else {
return 'Unknown';
}
}

static List<PayoutTransaction> fromJsonList(List<dynamic> jsonList) {
return jsonList.map((e) => PayoutTransaction.fromJson(e)).toList();
}
}
16 changes: 16 additions & 0 deletions app/lib/gen/assets.gen.dart

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import 'package:friend_private/pages/apps/providers/add_app_provider.dart';
import 'package:friend_private/pages/home/page.dart';
import 'package:friend_private/pages/conversation_detail/conversation_detail_provider.dart';
import 'package:friend_private/pages/onboarding/wrapper.dart';
import 'package:friend_private/pages/settings/creator_profile/creator_profile_provider.dart';
import 'package:friend_private/providers/app_provider.dart';
import 'package:friend_private/providers/auth_provider.dart';
import 'package:friend_private/providers/calendar_provider.dart';
Expand Down Expand Up @@ -203,6 +204,7 @@ class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
update: (BuildContext context, value, AddAppProvider? previous) =>
(previous?..setAppProvider(value)) ?? AddAppProvider(),
),
ChangeNotifierProvider(create: (context) => CreatorProfileProvider()),
],
builder: (context, child) {
return WithForegroundTask(
Expand Down
Loading