init
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../services/auth_controller.dart';
|
||||
|
||||
class AuthFlow extends StatefulWidget {
|
||||
const AuthFlow({super.key});
|
||||
|
||||
@override
|
||||
State<AuthFlow> createState() => _AuthFlowState();
|
||||
}
|
||||
|
||||
class _AuthFlowState extends State<AuthFlow> {
|
||||
final _phone = TextEditingController();
|
||||
final _password = TextEditingController();
|
||||
bool _staff = false;
|
||||
bool _waiting = false;
|
||||
Timer? _poll;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_poll?.cancel();
|
||||
_phone.dispose();
|
||||
_password.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _staffLogin() async {
|
||||
final auth = context.read<AuthController>();
|
||||
try {
|
||||
await auth.staffLogin(_phone.text.trim(), _password.text);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> _dealerLogin() async {
|
||||
final auth = context.read<AuthController>();
|
||||
try {
|
||||
final started = await auth.startDealerLogin(_phone.text.trim());
|
||||
if (!mounted) return;
|
||||
setState(() => _waiting = true);
|
||||
_poll = Timer.periodic(
|
||||
const Duration(seconds: 2),
|
||||
(_) => _check(started.requestId, started.secret),
|
||||
);
|
||||
await _check(started.requestId, started.secret);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
Future<void> _check(String requestId, String secret) async {
|
||||
final auth = context.read<AuthController>();
|
||||
try {
|
||||
final status = await auth.pollDealerLogin(requestId, secret);
|
||||
if (!mounted) return;
|
||||
if (status == 'pending') return;
|
||||
_poll?.cancel();
|
||||
if (status != 'approved') setState(() => _waiting = false);
|
||||
} catch (_) {
|
||||
if (!mounted) return;
|
||||
_poll?.cancel();
|
||||
setState(() => _waiting = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _cancelWait() {
|
||||
_poll?.cancel();
|
||||
context.read<AuthController>().error = null;
|
||||
setState(() => _waiting = false);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final auth = context.watch<AuthController>();
|
||||
final theme = Theme.of(context);
|
||||
return Scaffold(
|
||||
body: SafeArea(
|
||||
child: Align(
|
||||
alignment: Alignment.topCenter,
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 440),
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(24, 48, 24, 24),
|
||||
children: [
|
||||
Center(
|
||||
child: ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: Image.asset(
|
||||
'assets/images/senaro_logo.png',
|
||||
width: 300,
|
||||
semanticLabel: 'Senaro',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Text(
|
||||
'Dealer Network',
|
||||
textAlign: TextAlign.center,
|
||||
style: theme.textTheme.headlineMedium,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_waiting
|
||||
? 'Waiting for a sales rep or super admin to approve ${_phone.text.trim()}.'
|
||||
: _staff
|
||||
? 'Staff sign in with the phone number and password on the account.'
|
||||
: 'Dealers sign in with the phone number already registered for the dealership.',
|
||||
style: theme.textTheme.bodyLarge,
|
||||
),
|
||||
const SizedBox(height: 28),
|
||||
if (_waiting)
|
||||
const Center(child: CircularProgressIndicator())
|
||||
else ...[
|
||||
SegmentedButton<bool>(
|
||||
segments: const [
|
||||
ButtonSegment(value: false, label: Text('Dealer')),
|
||||
ButtonSegment(value: true, label: Text('Staff')),
|
||||
],
|
||||
selected: {_staff},
|
||||
onSelectionChanged: auth.busy
|
||||
? null
|
||||
: (value) => setState(() => _staff = value.first),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
TextField(
|
||||
controller: _phone,
|
||||
keyboardType: TextInputType.phone,
|
||||
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Phone',
|
||||
hintText: '071xxxxxxx',
|
||||
),
|
||||
onSubmitted: (_) => _staff ? _staffLogin() : _dealerLogin(),
|
||||
),
|
||||
if (_staff) ...[
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _password,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(labelText: 'Password'),
|
||||
onSubmitted: (_) => _staffLogin(),
|
||||
),
|
||||
],
|
||||
],
|
||||
if (auth.error != null) ...[
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
auth.error!,
|
||||
style: TextStyle(color: theme.colorScheme.error),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
if (_waiting)
|
||||
TextButton(
|
||||
onPressed: _cancelWait,
|
||||
child: const Text('Cancel'),
|
||||
)
|
||||
else
|
||||
FilledButton(
|
||||
onPressed: auth.busy
|
||||
? null
|
||||
: (_staff ? _staffLogin : _dealerLogin),
|
||||
child: Text(
|
||||
auth.busy
|
||||
? 'Please wait'
|
||||
: (_staff ? 'Sign in' : 'Request login'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user