import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:supabase_flutter/supabase_flutter.dart'; import 'supabase_provider.dart'; import '../utils/app_time.dart'; /// Admin user query parameters for server-side pagination. class AdminUserQuery { /// Creates admin user query parameters. const AdminUserQuery({ this.offset = 0, this.limit = 50, this.searchQuery = '', }); /// Offset for pagination. final int offset; /// Number of items per page (default: 50). final int limit; /// Full text search query. final String searchQuery; AdminUserQuery copyWith({int? offset, int? limit, String? searchQuery}) { return AdminUserQuery( offset: offset ?? this.offset, limit: limit ?? this.limit, searchQuery: searchQuery ?? this.searchQuery, ); } } final adminUserQueryProvider = StateProvider( (ref) => const AdminUserQuery(), ); final adminUserControllerProvider = Provider((ref) { final client = ref.watch(supabaseClientProvider); return AdminUserController(client); }); class AdminUserStatus { AdminUserStatus({required this.email, required this.bannedUntil}); final String? email; final DateTime? bannedUntil; bool get isLocked { if (bannedUntil == null) return false; return bannedUntil!.isAfter(AppTime.now()); } } class AdminUserController { AdminUserController(this._client); final SupabaseClient _client; Future updateProfile({ required String userId, required String fullName, required String role, String religion = 'catholic', }) async { await _client .from('profiles') .update({'full_name': fullName, 'role': role, 'religion': religion}) .eq('id', userId); } Future updateRole({ required String userId, required String role, }) async { await _client.from('profiles').update({'role': role}).eq('id', userId); } // Centralized helper that calls the admin Edge Function and surfaces // a clear error for 401/bad_jwt so the UI can react (sign out/reauth). Future _invokeAdminFunction( String action, Map payload, ) async { final accessToken = _client.auth.currentSession?.accessToken; final response = await _client.functions.invoke( 'admin_user_management', body: {'action': action, ...payload}, headers: accessToken == null ? null : {'Authorization': 'Bearer $accessToken'}, ); if (response.status == 401) { // If the gateway rejects the JWT, proactively clear the local session // so the app can re-authenticate and obtain a valid Supabase token. try { await _client.auth.signOut(); } catch (_) { // ignore sign-out errors } throw Exception( 'Unauthorized: invalid or expired session token (bad_jwt)', ); } if (response.status != 200) { throw Exception(response.data ?? 'Admin request failed'); } return response.data; } Future setPassword({ required String userId, required String password, }) async { if (password.length < 8) { throw Exception('Password must be at least 8 characters'); } await _invokeAdminFunction('set_password', { 'userId': userId, 'password': password, }); } Future setLock({required String userId, required bool locked}) async { await _invokeAdminFunction('set_lock', { 'userId': userId, 'locked': locked, }); } Future fetchStatus(String userId) async { final data = await _invokeAdminFunction('get_user', {'userId': userId}); final user = (data as Map?)?['user'] as Map?; final email = user?['email'] as String?; DateTime? bannedUntil; final bannedRaw = user?['banned_until']; if (bannedRaw is String) { bannedUntil = DateTime.tryParse(bannedRaw); } else if (bannedRaw is DateTime) { bannedUntil = bannedRaw; } return AdminUserStatus( email: email, bannedUntil: bannedUntil == null ? null : AppTime.toAppTime(bannedUntil), ); } Future>> listUsers(AdminUserQuery q) async { final data = await _invokeAdminFunction('list_users', { 'offset': q.offset, 'limit': q.limit, 'searchQuery': q.searchQuery, }); final users = (data is Map && data['users'] is List) ? (data['users'] as List).cast>() : >[]; return users; } } final adminUserStatusProvider = FutureProvider.family .autoDispose((ref, userId) { return ref.watch(adminUserControllerProvider).fetchStatus(userId); }); final adminUsersProvider = FutureProvider.autoDispose>>((ref) { final q = ref.watch(adminUserQueryProvider); final ctrl = ref.watch(adminUserControllerProvider); return ctrl.listUsers(q); });