cyberhybridhub/lib/admin/widgets/admin_gate.dart
2026-05-31 11:17:12 -05:00

145 lines
4.0 KiB
Dart

import 'package:flutter/material.dart';
import '../../theme/app_theme.dart';
import '../services/market_history_admin_api.dart';
enum AdminGateStatus { loading, authorized, forbidden, unauthorized, error }
/// Probes admin API access before showing [child].
///
/// Calls `GET /v1/admin/market-history/sync-runs?limit=1`.
/// Returns 403 → not authorized UI; 200 → [child].
class AdminGate extends StatefulWidget {
const AdminGate({
super.key,
required this.child,
this.api,
});
final Widget child;
final MarketHistoryAdminApi? api;
@override
State<AdminGate> createState() => _AdminGateState();
}
class _AdminGateState extends State<AdminGate> {
AdminGateStatus _status = AdminGateStatus.loading;
String? _errorMessage;
@override
void initState() {
super.initState();
_probe();
}
Future<void> _probe() async {
final MarketHistoryAdminApi api = widget.api ?? MarketHistoryAdminApi();
try {
await api.fetchSyncRuns(limit: 1);
if (!mounted) {
return;
}
setState(() => _status = AdminGateStatus.authorized);
} on MarketHistoryAdminApiException catch (e) {
if (!mounted) {
return;
}
setState(() {
_errorMessage = e.message;
_status = switch (e.statusCode) {
401 => AdminGateStatus.unauthorized,
403 => AdminGateStatus.forbidden,
_ => AdminGateStatus.error,
};
});
} catch (e) {
if (!mounted) {
return;
}
setState(() {
_status = AdminGateStatus.error;
_errorMessage = e.toString();
});
}
}
@override
Widget build(BuildContext context) {
return switch (_status) {
AdminGateStatus.loading => const Scaffold(
key: Key('admin-gate-loading'),
body: Center(
child: CircularProgressIndicator(color: AppColors.accent),
),
),
AdminGateStatus.authorized => widget.child,
AdminGateStatus.forbidden => _messageScaffold(
key: const Key('admin-gate-forbidden'),
title: 'Not authorized',
message: 'Your account does not have admin access.',
icon: Icons.lock_outline,
),
AdminGateStatus.unauthorized => _messageScaffold(
key: const Key('admin-gate-unauthorized'),
title: 'Sign in required',
message: 'Sign in again to access the admin portal.',
icon: Icons.login,
),
AdminGateStatus.error => _messageScaffold(
key: const Key('admin-gate-error'),
title: 'Admin check failed',
message: _errorMessage ?? 'Unknown error',
icon: Icons.error_outline,
showRetry: true,
),
};
}
Widget _messageScaffold({
required Key key,
required String title,
required String message,
required IconData icon,
bool showRetry = false,
}) {
return Scaffold(
key: key,
appBar: AppBar(title: const Text('Admin portal')),
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Icon(icon, size: 40, color: AppColors.textSecondary),
const SizedBox(height: 12),
Text(
title,
style: Theme.of(context).textTheme.titleLarge,
textAlign: TextAlign.center,
),
const SizedBox(height: 8),
Text(
message,
style: const TextStyle(color: AppColors.textSecondary),
textAlign: TextAlign.center,
),
if (showRetry) ...<Widget>[
const SizedBox(height: 16),
FilledButton(
onPressed: () {
setState(() => _status = AdminGateStatus.loading);
_probe();
},
child: const Text('Retry'),
),
],
],
),
),
),
);
}
}