feat: Phase 1 critical gaps — gamification API, Razorpay checkout, Google OAuth, notifications
- Fix gamification endpoints to use Node.js server (app.eventifyplus.com) - Replace 6 mock gamification methods with real API calls (dashboard, leaderboard, shop, redeem, submit) - Add booking models, service, payment service (Razorpay), checkout provider - Add 3-step CheckoutScreen with Razorpay native modal integration - Add Google OAuth login (Flutter + Django backend) - Add full notifications system (Django model + 3 endpoints + Flutter UI) - Register CheckoutProvider, NotificationProvider in main.dart MultiProvider - Wire notification bell in HomeScreen app bar - Add razorpay_flutter ^1.3.7 and google_sign_in ^6.2.2 packages
This commit is contained in:
@@ -94,41 +94,162 @@ class UserGamificationProfile {
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// LeaderboardEntry
|
||||
// LeaderboardEntry — maps from Node.js API response fields
|
||||
// ---------------------------------------------------------------------------
|
||||
class LeaderboardEntry {
|
||||
final int rank;
|
||||
final String username;
|
||||
final String? avatarUrl;
|
||||
final int lifetimeEp;
|
||||
final int monthlyPoints;
|
||||
final ContributorTier tier;
|
||||
final int eventsCount;
|
||||
final bool isCurrentUser;
|
||||
final String? district;
|
||||
|
||||
const LeaderboardEntry({
|
||||
required this.rank,
|
||||
required this.username,
|
||||
this.avatarUrl,
|
||||
required this.lifetimeEp,
|
||||
this.monthlyPoints = 0,
|
||||
required this.tier,
|
||||
required this.eventsCount,
|
||||
this.isCurrentUser = false,
|
||||
this.district,
|
||||
});
|
||||
|
||||
factory LeaderboardEntry.fromJson(Map<String, dynamic> json) {
|
||||
final ep = (json['lifetime_ep'] as int?) ?? 0;
|
||||
// Node.js API returns 'points' for lifetime EP and 'name' for username
|
||||
final ep = (json['points'] as num?)?.toInt() ?? (json['lifetime_ep'] as num?)?.toInt() ?? 0;
|
||||
final tierStr = json['level'] as String? ?? json['tier'] as String?;
|
||||
return LeaderboardEntry(
|
||||
rank: (json['rank'] as int?) ?? 0,
|
||||
username: json['username'] as String? ?? '',
|
||||
rank: (json['rank'] as num?)?.toInt() ?? 0,
|
||||
username: json['name'] as String? ?? json['username'] as String? ?? '',
|
||||
avatarUrl: json['avatar_url'] as String?,
|
||||
lifetimeEp: ep,
|
||||
tier: tierFromEp(ep),
|
||||
eventsCount: (json['events_count'] as int?) ?? 0,
|
||||
monthlyPoints: (json['monthlyPoints'] as num?)?.toInt() ?? 0,
|
||||
tier: tierStr != null ? _tierFromString(tierStr) : tierFromEp(ep),
|
||||
eventsCount: (json['eventsAdded'] as num?)?.toInt() ?? (json['events_count'] as num?)?.toInt() ?? 0,
|
||||
isCurrentUser: (json['is_current_user'] as bool?) ?? false,
|
||||
district: json['district'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse tier string from API (e.g. "Gold") to enum.
|
||||
ContributorTier _tierFromString(String s) {
|
||||
switch (s.toLowerCase()) {
|
||||
case 'diamond': return ContributorTier.DIAMOND;
|
||||
case 'platinum': return ContributorTier.PLATINUM;
|
||||
case 'gold': return ContributorTier.GOLD;
|
||||
case 'silver': return ContributorTier.SILVER;
|
||||
default: return ContributorTier.BRONZE;
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// CurrentUserStats — from leaderboard API's currentUser field
|
||||
// ---------------------------------------------------------------------------
|
||||
class CurrentUserStats {
|
||||
final int rank;
|
||||
final int points;
|
||||
final int monthlyPoints;
|
||||
final String level;
|
||||
final int rewardCycleDays;
|
||||
final int eventsAdded;
|
||||
final String? district;
|
||||
|
||||
const CurrentUserStats({
|
||||
required this.rank,
|
||||
required this.points,
|
||||
this.monthlyPoints = 0,
|
||||
required this.level,
|
||||
this.rewardCycleDays = 0,
|
||||
this.eventsAdded = 0,
|
||||
this.district,
|
||||
});
|
||||
|
||||
factory CurrentUserStats.fromJson(Map<String, dynamic> json) {
|
||||
return CurrentUserStats(
|
||||
rank: (json['rank'] as num?)?.toInt() ?? 0,
|
||||
points: (json['points'] as num?)?.toInt() ?? 0,
|
||||
monthlyPoints: (json['monthlyPoints'] as num?)?.toInt() ?? 0,
|
||||
level: json['level'] as String? ?? 'Bronze',
|
||||
rewardCycleDays: (json['rewardCycleDays'] as num?)?.toInt() ?? 0,
|
||||
eventsAdded: (json['eventsAdded'] as num?)?.toInt() ?? 0,
|
||||
district: json['district'] as String?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// LeaderboardResponse — wraps the full leaderboard API response
|
||||
// ---------------------------------------------------------------------------
|
||||
class LeaderboardResponse {
|
||||
final List<LeaderboardEntry> entries;
|
||||
final CurrentUserStats? currentUser;
|
||||
final int totalParticipants;
|
||||
|
||||
const LeaderboardResponse({
|
||||
required this.entries,
|
||||
this.currentUser,
|
||||
this.totalParticipants = 0,
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// SubmissionModel — event submissions from dashboard API
|
||||
// ---------------------------------------------------------------------------
|
||||
class SubmissionModel {
|
||||
final String id;
|
||||
final String eventName;
|
||||
final String category;
|
||||
final String status; // PENDING, APPROVED, REJECTED
|
||||
final String? district;
|
||||
final int epAwarded;
|
||||
final DateTime createdAt;
|
||||
final List<String> images;
|
||||
|
||||
const SubmissionModel({
|
||||
required this.id,
|
||||
required this.eventName,
|
||||
this.category = '',
|
||||
required this.status,
|
||||
this.district,
|
||||
this.epAwarded = 0,
|
||||
required this.createdAt,
|
||||
this.images = const [],
|
||||
});
|
||||
|
||||
factory SubmissionModel.fromJson(Map<String, dynamic> json) {
|
||||
final rawImages = json['images'] as List? ?? [];
|
||||
return SubmissionModel(
|
||||
id: (json['id'] ?? json['submission_id'] ?? '').toString(),
|
||||
eventName: json['event_name'] as String? ?? '',
|
||||
category: json['category'] as String? ?? '',
|
||||
status: json['status'] as String? ?? 'PENDING',
|
||||
district: json['district'] as String?,
|
||||
epAwarded: (json['total_ep_awarded'] as num?)?.toInt() ?? (json['ep_awarded'] as num?)?.toInt() ?? 0,
|
||||
createdAt: DateTime.tryParse(json['created_at'] as String? ?? '') ?? DateTime.now(),
|
||||
images: rawImages.map((e) => e.toString()).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DashboardResponse — wraps the full dashboard API response
|
||||
// ---------------------------------------------------------------------------
|
||||
class DashboardResponse {
|
||||
final UserGamificationProfile profile;
|
||||
final List<SubmissionModel> submissions;
|
||||
|
||||
const DashboardResponse({
|
||||
required this.profile,
|
||||
this.submissions = const [],
|
||||
});
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// ShopItem — mirrors `RedeemShopItem` table
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user