forked from baron/baron-sso
admin page add
This commit is contained in:
232
frontend/lib/features/admin/presentation/create_user_screen.dart
Normal file
232
frontend/lib/features/admin/presentation/create_user_screen.dart
Normal file
@@ -0,0 +1,232 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import '../../../../core/services/auth_proxy_service.dart';
|
||||
|
||||
class CreateUserScreen extends StatefulWidget {
|
||||
const CreateUserScreen({super.key});
|
||||
|
||||
@override
|
||||
State<CreateUserScreen> createState() => _CreateUserScreenState();
|
||||
}
|
||||
|
||||
class _CreateUserScreenState extends State<CreateUserScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final TextEditingController _loginIdController = TextEditingController();
|
||||
final TextEditingController _emailController = TextEditingController();
|
||||
final TextEditingController _phoneController = TextEditingController();
|
||||
final TextEditingController _nameController = TextEditingController();
|
||||
|
||||
bool _isLoading = false;
|
||||
bool _isAuthorized = false;
|
||||
String? _verifiedAdminPassword;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _verifyAccess());
|
||||
}
|
||||
|
||||
Future<void> _verifyAccess() async {
|
||||
final passwordController = TextEditingController();
|
||||
|
||||
// Show blocking dialog
|
||||
final String? inputPassword = await showDialog<String>(
|
||||
context: context,
|
||||
barrierDismissible: false, // User must enter password or leave
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text("Admin Authentication Required"),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Text("Please enter the admin password to access this page."),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: passwordController,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Password",
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
autofocus: true,
|
||||
onSubmitted: (value) => Navigator.pop(context, value),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.pop(context, null), // Cancel
|
||||
child: const Text("Cancel"),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.pop(context, passwordController.text),
|
||||
child: const Text("Enter"),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
// If cancelled or empty
|
||||
if (inputPassword == null || inputPassword.isEmpty) {
|
||||
if (mounted) context.go('/dashboard'); // Kick out
|
||||
return;
|
||||
}
|
||||
|
||||
// Verify against Backend
|
||||
setState(() => _isLoading = true);
|
||||
final isValid = await AuthProxyService.checkAdminAuth(inputPassword);
|
||||
setState(() => _isLoading = false);
|
||||
|
||||
if (isValid) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isAuthorized = true;
|
||||
_verifiedAdminPassword = inputPassword;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Invalid Password. Access Denied.'), backgroundColor: Colors.red),
|
||||
);
|
||||
context.go('/dashboard'); // Kick out
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_loginIdController.dispose();
|
||||
_emailController.dispose();
|
||||
_phoneController.dispose();
|
||||
_nameController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _submit() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
if (_verifiedAdminPassword == null) return; // Should not happen
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
try {
|
||||
await AuthProxyService.createUser(
|
||||
loginId: _loginIdController.text.trim(),
|
||||
adminPassword: _verifiedAdminPassword!,
|
||||
email: _emailController.text.trim().isEmpty ? null : _emailController.text.trim(),
|
||||
phone: _phoneController.text.trim().isEmpty ? null : _phoneController.text.trim(),
|
||||
displayName: _nameController.text.trim().isEmpty ? null : _nameController.text.trim(),
|
||||
);
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('User created successfully!'), backgroundColor: Colors.green),
|
||||
);
|
||||
_formKey.currentState!.reset();
|
||||
_loginIdController.clear();
|
||||
_emailController.clear();
|
||||
_phoneController.clear();
|
||||
_nameController.clear();
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('Error: $e'), backgroundColor: Colors.red),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Hide content until authorized
|
||||
if (!_isAuthorized) {
|
||||
return const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Create User'),
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back),
|
||||
onPressed: () => context.go('/dashboard'),
|
||||
),
|
||||
),
|
||||
body: Center(
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(maxWidth: 600),
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
const Text(
|
||||
"Create New User",
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
TextFormField(
|
||||
controller: _loginIdController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Login ID (Required)",
|
||||
border: OutlineInputBorder(),
|
||||
helperText: "Unique identifier (Email or Phone)",
|
||||
),
|
||||
validator: (value) => value == null || value.isEmpty ? 'Please enter Login ID' : null,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Display Name",
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.person),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
TextFormField(
|
||||
controller: _emailController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Email",
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.email),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
TextFormField(
|
||||
controller: _phoneController,
|
||||
decoration: const InputDecoration(
|
||||
labelText: "Phone Number",
|
||||
border: OutlineInputBorder(),
|
||||
prefixIcon: Icon(Icons.phone),
|
||||
helperText: "Start with 010 (e.g., 010-1234-5678)",
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
|
||||
FilledButton(
|
||||
onPressed: _isLoading ? null : _submit,
|
||||
style: FilledButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
),
|
||||
child: _isLoading
|
||||
? const CircularProgressIndicator(color: Colors.white)
|
||||
: const Text("Create User"),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user