8bb192fe54
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>
92 lines
2.8 KiB
Dart
92 lines
2.8 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../api/endpoints/admin_invites.dart';
|
|
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));
|
|
});
|
|
|
|
final adminQuarantineApiProvider =
|
|
FutureProvider<AdminQuarantineApi>((ref) async {
|
|
return AdminQuarantineApi(await ref.watch(dioProvider.future));
|
|
});
|
|
|
|
final adminUsersApiProvider = FutureProvider<AdminUsersApi>((ref) async {
|
|
return AdminUsersApi(await ref.watch(dioProvider.future));
|
|
});
|
|
|
|
final adminInvitesApiProvider = FutureProvider<AdminInvitesApi>((ref) async {
|
|
return AdminInvitesApi(await ref.watch(dioProvider.future));
|
|
});
|
|
|
|
class AdminCounts {
|
|
const AdminCounts({
|
|
required this.requests,
|
|
required this.quarantine,
|
|
required this.users,
|
|
});
|
|
final int requests;
|
|
final int quarantine;
|
|
final int users;
|
|
}
|
|
|
|
/// Fans out to the three list endpoints in parallel and rolls them up
|
|
/// into `{requests, quarantine, users}` for the landing screen badges.
|
|
final adminCountsProvider = FutureProvider<AdminCounts>((ref) async {
|
|
final requestsApi = await ref.watch(adminRequestsApiProvider.future);
|
|
final quarantineApi = await ref.watch(adminQuarantineApiProvider.future);
|
|
final usersApi = await ref.watch(adminUsersApiProvider.future);
|
|
final results = await Future.wait([
|
|
requestsApi.list(),
|
|
quarantineApi.list(),
|
|
usersApi.list(),
|
|
]);
|
|
return AdminCounts(
|
|
requests: (results[0] as List).length,
|
|
quarantine: (results[1] as List).length,
|
|
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);
|