145 lines
4.0 KiB
Dart
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'),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|