import 'package:flutter/material.dart'; import '../api.dart'; import '../session.dart'; import '../theme.dart'; import '../widgets.dart'; /// Email-code sign-in. Codes are only sent to existing accounts, and only admins get in. class SignInScreen extends StatefulWidget { const SignInScreen({super.key}); @override State createState() => _SignInScreenState(); } class _SignInScreenState extends State { final _email = TextEditingController(); final _code = TextEditingController(); bool _codeSent = false; bool _busy = false; String? _error; @override void dispose() { _email.dispose(); _code.dispose(); super.dispose(); } String get _emailText => _email.text.trim().toLowerCase(); Future _run(Future Function() job) async { setState(() { _busy = true; _error = null; }); try { await job(); } catch (e) { _error = errorText(e); } finally { if (mounted) setState(() => _busy = false); } } void _sendCode() { if (!_emailText.contains('@')) { setState(() => _error = 'Enter your admin email address.'); return; } _run(() async { await AdminApi.sendCode(_emailText); _codeSent = true; _code.clear(); }); } void _verify() { if (_code.text.trim().length < 6) { setState(() => _error = 'Enter the 6-digit code from the email.'); return; } _run(() => AdminApi.verifyCode(_emailText, _code.text.trim())); } @override Widget build(BuildContext context) { final session = AdminSession.instance; return Scaffold( body: ListenableBuilder( listenable: session, builder: (context, _) { if (session.state == SessionState.checking) return const Center(child: CircularProgressIndicator()); final message = _error ?? session.refusal; return Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 400), child: Card( child: Padding( padding: const EdgeInsets.all(28), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Container( width: 56, height: 56, alignment: Alignment.center, decoration: const BoxDecoration(color: NkColors.sun, shape: BoxShape.circle), child: const Text('NK', style: TextStyle(color: NkColors.ground, fontWeight: FontWeight.w900, fontSize: 20)), ), const SizedBox(height: 20), Text('Nanga Kathai Admin', style: Theme.of(context).textTheme.headlineSmall), const SizedBox(height: 6), Text( _codeSent ? 'We emailed a 6-digit code to $_emailText.' : 'Sign in with your admin email. We\'ll send you a code.', style: Theme.of(context).textTheme.bodySmall, ), const SizedBox(height: 24), if (!_codeSent) TextField( controller: _email, autofocus: true, keyboardType: TextInputType.emailAddress, autofillHints: const [AutofillHints.email], decoration: const InputDecoration(labelText: 'Email'), onSubmitted: (_) => _sendCode(), ) else TextField( controller: _code, autofocus: true, keyboardType: TextInputType.number, autofillHints: const [AutofillHints.oneTimeCode], maxLength: 6, style: const TextStyle(fontSize: 22, letterSpacing: 8, fontWeight: FontWeight.w700), decoration: const InputDecoration(labelText: 'Code', counterText: ''), onSubmitted: (_) => _verify(), ), if (message != null) ...[ const SizedBox(height: 12), Text(message, style: const TextStyle(color: NkColors.roofRed)), ], const SizedBox(height: 20), FilledButton( onPressed: _busy ? null : (_codeSent ? _verify : _sendCode), child: _busy ? const SizedBox.square( dimension: 20, child: CircularProgressIndicator(strokeWidth: 2)) : Text(_codeSent ? 'Sign in' : 'Email me a code'), ), if (_codeSent) ...[ const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ TextButton( onPressed: _busy ? null : () => setState(() => _codeSent = false), child: const Text('Use another email'), ), TextButton(onPressed: _busy ? null : _sendCode, child: const Text('Send again')), ], ), ], ], ), ), ), ), ), ); }, ), ); } }