1
0
forked from baron/baron-sso
Files
baron-sso/userfront/lib/features/admin/presentation/create_user_screen.dart

251 lines
7.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/services/auth_proxy_service.dart';
import '../../../../core/i18n/locale_utils.dart';
import '../../../../core/ui/toast_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(buildLocalizedHomePath(Uri.base)); // 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) {
ToastService.error('Invalid Password. Access Denied.');
context.go(buildLocalizedHomePath(Uri.base)); // 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);
String loginId = _loginIdController.text.trim();
if (!loginId.contains('@')) {
loginId = loginId.replaceAll(RegExp(r'[-\s]'), '');
if (loginId.startsWith('010')) {
loginId = '+82${loginId.substring(1)}';
}
}
String? phone = _phoneController.text.trim().isEmpty
? null
: _phoneController.text.trim();
if (phone != null && !phone.contains('@')) {
phone = phone.replaceAll(RegExp(r'[-\s]'), '');
if (phone.startsWith('010')) {
phone = '+82${phone.substring(1)}';
}
}
try {
await AuthProxyService.createUser(
loginId: loginId,
adminPassword: _verifiedAdminPassword!,
email: _emailController.text.trim().isEmpty
? null
: _emailController.text.trim(),
phone: phone,
displayName: _nameController.text.trim().isEmpty
? null
: _nameController.text.trim(),
);
if (mounted) {
ToastService.success('User created successfully!');
_formKey.currentState!.reset();
_loginIdController.clear();
_emailController.clear();
_phoneController.clear();
_nameController.clear();
}
} catch (e) {
if (mounted) {
ToastService.error('Error: $e');
}
} 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(buildLocalizedHomePath(Uri.base)),
),
),
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"),
),
],
),
),
),
),
);
}
}