feat(flutter/admin): adminRequestsProvider with optimistic approve/reject

AsyncNotifier removes the row optimistically on approve/reject; rolls
back to the pre-mutation list on server error. Invalidates
adminCountsProvider so the landing badge stays accurate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-08 21:54:05 -04:00
parent 408eed1f3d
commit 8bb192fe54
@@ -5,6 +5,7 @@ import '../api/endpoints/admin_quarantine.dart';
import '../api/endpoints/admin_requests.dart';
import '../api/endpoints/admin_users.dart';
import '../library/library_providers.dart' show dioProvider;
import '../models/admin_request.dart';
final adminRequestsApiProvider = FutureProvider<AdminRequestsApi>((ref) async {
return AdminRequestsApi(await ref.watch(dioProvider.future));
@@ -51,3 +52,40 @@ final adminCountsProvider = FutureProvider<AdminCounts>((ref) async {
users: (results[2] as List).length,
);
});
class AdminRequestsController extends AsyncNotifier<List<AdminRequest>> {
@override
Future<List<AdminRequest>> build() async {
final api = await ref.watch(adminRequestsApiProvider.future);
return api.list();
}
Future<void> approve(String id) async {
await _decide(id, (api) => api.approve(id));
}
Future<void> reject(String id) async {
await _decide(id, (api) => api.reject(id));
}
Future<void> _decide(
String id,
Future<void> Function(AdminRequestsApi api) action,
) async {
final api = await ref.read(adminRequestsApiProvider.future);
final current = state.value ?? const <AdminRequest>[];
state = AsyncData(current.where((r) => r.id != id).toList());
try {
await action(api);
// Refresh landing badge.
ref.invalidate(adminCountsProvider);
} catch (e, st) {
state = AsyncData(current);
Error.throwWithStackTrace(e, st);
}
}
}
final adminRequestsProvider =
AsyncNotifierProvider<AdminRequestsController, List<AdminRequest>>(
AdminRequestsController.new);