feat: PDF zoom & external link fix, admin contact messages inbox, and in-app user management
This commit is contained in:
@@ -122,6 +122,9 @@ class AuthService extends ChangeNotifier {
|
||||
_auth.authStateChanges().listen((User? user) async {
|
||||
_user = user;
|
||||
await _checkAdminClaim(user);
|
||||
if (user != null && !user.isAnonymous) {
|
||||
await _syncUserProfile(user);
|
||||
}
|
||||
notifyListeners();
|
||||
});
|
||||
|
||||
@@ -130,6 +133,33 @@ class AuthService extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _syncUserProfile(User user, {String? firstName, String? lastName}) async {
|
||||
if (user.isAnonymous) return;
|
||||
try {
|
||||
final cleanEmail = user.email?.trim().toLowerCase() ?? '';
|
||||
if (cleanEmail.isEmpty) return;
|
||||
|
||||
final cleanFirst = firstName ?? this.firstName;
|
||||
final cleanLast = lastName ?? this.lastName;
|
||||
final name = user.displayName ?? '$cleanFirst $cleanLast'.trim();
|
||||
|
||||
await FirebaseFirestore.instance.collection('Users').doc(user.uid).set({
|
||||
'uid': user.uid,
|
||||
'email': cleanEmail,
|
||||
'displayName': name,
|
||||
'firstName': cleanFirst,
|
||||
'lastName': cleanLast,
|
||||
'emailVerified': user.emailVerified,
|
||||
'lastLogin': FieldValue.serverTimestamp(),
|
||||
if (_isAdmin) 'isAdmin': true,
|
||||
}, SetOptions(merge: true));
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
debugPrint('Error syncing user profile in auth service: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies community access code
|
||||
Future<bool> verifyCommunityCode(String enteredCode) async {
|
||||
final cleanCode = enteredCode.trim().toLowerCase();
|
||||
@@ -165,6 +195,10 @@ class AuthService extends ChangeNotifier {
|
||||
password: password,
|
||||
);
|
||||
_user = credential.user;
|
||||
await _checkAdminClaim(_user);
|
||||
if (_user != null) {
|
||||
await _syncUserProfile(_user!);
|
||||
}
|
||||
notifyListeners();
|
||||
return null; // success
|
||||
} on FirebaseAuthException catch (e) {
|
||||
@@ -200,6 +234,10 @@ class AuthService extends ChangeNotifier {
|
||||
await prefs.setString('user_first_name', cleanFirst);
|
||||
await prefs.setString('user_last_name', cleanLast);
|
||||
|
||||
await _checkAdminClaim(_user);
|
||||
await _syncUserProfile(_user!,
|
||||
firstName: cleanFirst, lastName: cleanLast);
|
||||
|
||||
// Send email verification so user verifies they own the email address
|
||||
try {
|
||||
await _user!.sendEmailVerification();
|
||||
|
||||
@@ -63,4 +63,138 @@ class FirestoreService {
|
||||
'status': 'Neu',
|
||||
});
|
||||
}
|
||||
|
||||
/// Stream of all contact messages (for admins)
|
||||
Stream<QuerySnapshot<Map<String, dynamic>>> getContactMessagesStream() {
|
||||
return _firestore
|
||||
.collection('ContactMessages')
|
||||
.orderBy('createdDate', descending: true)
|
||||
.snapshots();
|
||||
}
|
||||
|
||||
/// Update the status of a contact message
|
||||
Future<void> updateContactMessageStatus(String docId, String status) async {
|
||||
await _firestore.collection('ContactMessages').doc(docId).update({
|
||||
'status': status,
|
||||
'statusUpdatedAt': FieldValue.serverTimestamp(),
|
||||
});
|
||||
}
|
||||
|
||||
/// Delete a contact message
|
||||
Future<void> deleteContactMessage(String docId) async {
|
||||
await _firestore.collection('ContactMessages').doc(docId).delete();
|
||||
}
|
||||
|
||||
/// Stream of all registered users
|
||||
Stream<QuerySnapshot<Map<String, dynamic>>> getUsersStream() {
|
||||
return _firestore.collection('Users').snapshots();
|
||||
}
|
||||
|
||||
/// Stream of all admin documents
|
||||
Stream<QuerySnapshot<Map<String, dynamic>>> getAdminsStream() {
|
||||
return _firestore.collection('Admins').snapshots();
|
||||
}
|
||||
|
||||
/// Upsert a registered user profile into Firestore
|
||||
Future<void> syncUserProfile({
|
||||
required String uid,
|
||||
required String email,
|
||||
String? displayName,
|
||||
String? firstName,
|
||||
String? lastName,
|
||||
bool? emailVerified,
|
||||
bool? isAdmin,
|
||||
}) async {
|
||||
try {
|
||||
final docRef = _firestore.collection('Users').doc(uid);
|
||||
final doc = await docRef.get();
|
||||
final data = <String, dynamic>{
|
||||
'uid': uid,
|
||||
'email': email.trim().toLowerCase(),
|
||||
'lastLogin': FieldValue.serverTimestamp(),
|
||||
if (displayName != null && displayName.isNotEmpty)
|
||||
'displayName': displayName,
|
||||
if (firstName != null && firstName.isNotEmpty)
|
||||
'firstName': firstName,
|
||||
if (lastName != null && lastName.isNotEmpty)
|
||||
'lastName': lastName,
|
||||
if (emailVerified != null) 'emailVerified': emailVerified,
|
||||
if (isAdmin != null) 'isAdmin': isAdmin,
|
||||
};
|
||||
|
||||
if (!doc.exists) {
|
||||
data['createdAt'] = FieldValue.serverTimestamp();
|
||||
}
|
||||
|
||||
await docRef.set(data, SetOptions(merge: true));
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
debugPrint('Error syncing user profile to Firestore: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Promote or demote a user to Admin in Firestore
|
||||
Future<void> setAdminRole({
|
||||
required String uid,
|
||||
required String email,
|
||||
required bool makeAdmin,
|
||||
String? grantedBy,
|
||||
}) async {
|
||||
final cleanEmail = email.trim().toLowerCase();
|
||||
final adminRef = _firestore.collection('Admins').doc(uid);
|
||||
final userRef = _firestore.collection('Users').doc(uid);
|
||||
|
||||
if (makeAdmin) {
|
||||
// 1. Add UID to Admins collection
|
||||
await adminRef.set({
|
||||
'uid': uid,
|
||||
'email': cleanEmail,
|
||||
'grantedAt': FieldValue.serverTimestamp(),
|
||||
'grantedBy': grantedBy ?? '',
|
||||
}, SetOptions(merge: true));
|
||||
|
||||
// 2. Also ensure email doc exists if email is known
|
||||
if (cleanEmail.isNotEmpty) {
|
||||
await _firestore.collection('Admins').doc(cleanEmail).set({
|
||||
'uid': uid,
|
||||
'email': cleanEmail,
|
||||
'grantedAt': FieldValue.serverTimestamp(),
|
||||
'grantedBy': grantedBy ?? '',
|
||||
}, SetOptions(merge: true));
|
||||
}
|
||||
|
||||
// 3. Mark user document as admin
|
||||
await userRef.set({'isAdmin': true}, SetOptions(merge: true));
|
||||
} else {
|
||||
// 1. Remove UID from Admins collection
|
||||
await adminRef.delete();
|
||||
|
||||
// 2. Remove email from Admins collection if present
|
||||
if (cleanEmail.isNotEmpty) {
|
||||
try {
|
||||
await _firestore.collection('Admins').doc(cleanEmail).delete();
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// 3. Update user document
|
||||
await userRef.set({'isAdmin': false}, SetOptions(merge: true));
|
||||
}
|
||||
}
|
||||
|
||||
/// Add admin directly by UID or email (e.g. manual entry)
|
||||
Future<void> addAdminDirectly({
|
||||
required String identifier,
|
||||
required String email,
|
||||
String? grantedBy,
|
||||
}) async {
|
||||
final cleanId = identifier.trim();
|
||||
final cleanEmail = email.trim().toLowerCase();
|
||||
await _firestore.collection('Admins').doc(cleanId).set({
|
||||
'uid': cleanId,
|
||||
'email': cleanEmail,
|
||||
'grantedAt': FieldValue.serverTimestamp(),
|
||||
'grantedBy': grantedBy ?? '',
|
||||
}, SetOptions(merge: true));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user