Files
nearle_pos/lib/presentation/pos/widgets/admin_pin_dialog.dart

198 lines
6.0 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../app/providers.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/theme/app_dimens.dart';
import '../../../core/widgets/numeric_keypad.dart';
import '../../../domain/entities/store_account.dart';
/// Prompts for an admin PIN before a theft-sensitive cart action — taking a
/// scanned item back out of the bill, or clearing it — and resolves `true`
/// only once a PIN belonging to an [StaffRole.admin] account is verified.
///
/// A cashier can always start a brand new sale; what this exists to stop is
/// quietly taking something back out of a bill a customer has already been
/// shown, after it was rung up.
Future<bool> requireAdminPin(
BuildContext context,
WidgetRef ref, {
required String reason,
}) async {
final ok = await showDialog<bool>(
context: context,
barrierDismissible: false,
builder: (_) => _AdminPinDialog(reason: reason),
);
return ok ?? false;
}
class _AdminPinDialog extends ConsumerStatefulWidget {
const _AdminPinDialog({required this.reason});
final String reason;
@override
ConsumerState<_AdminPinDialog> createState() => _AdminPinDialogState();
}
class _AdminPinDialogState extends ConsumerState<_AdminPinDialog> {
String _pin = '';
String? _error;
bool _checking = false;
void _key(String digit) {
if (_checking || _pin.length >= 8) return;
setState(() {
_pin += digit;
_error = null;
});
}
void _backspace() {
if (_checking || _pin.isEmpty) return;
setState(() => _pin = _pin.substring(0, _pin.length - 1));
}
void _clear() {
if (_checking) return;
setState(() => _pin = '');
}
Future<void> _submit() async {
if (_pin.isEmpty || _checking) return;
setState(() {
_checking = true;
_error = null;
});
final store = ref.read(localStoreProvider);
final user = await store.staff.authenticate(_pin);
if (!mounted) return;
if (user == null) {
setState(() {
_checking = false;
_error = 'Incorrect PIN.';
_pin = '';
});
return;
}
if (user.role != StaffRole.admin) {
setState(() {
_checking = false;
_error = "${user.name}'s PIN is ${user.role.label.toLowerCase()}"
'this needs an admin.';
_pin = '';
});
return;
}
Navigator.of(context).pop(true);
}
@override
Widget build(BuildContext context) {
return Dialog(
shape: const RoundedRectangleBorder(borderRadius: AppRadius.brLg),
child: Padding(
padding: const EdgeInsets.all(AppSpacing.xl),
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 320),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
const Icon(Icons.lock_outline_rounded,
color: AppColors.danger, size: 20,),
const SizedBox(width: AppSpacing.sm),
const Expanded(
child: Text(
'Admin PIN required',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w700,
),
),
),
InkWell(
onTap: () => Navigator.of(context).pop(false),
borderRadius: AppRadius.brSm,
child: const Padding(
padding: EdgeInsets.all(4),
child: Icon(Icons.close_rounded,
size: 20, color: AppColors.textSecondary,),
),
),
],
),
const SizedBox(height: AppSpacing.xs),
Text(
widget.reason,
style: const TextStyle(
fontSize: 13,
color: AppColors.textSecondary,
height: 1.4,
),
),
const SizedBox(height: AppSpacing.lg),
SizedBox(
height: 20,
child: _pin.isEmpty
? const Center(
child: Text(
'Enter PIN',
style: TextStyle(
fontSize: 13,
color: AppColors.textTertiary,
),
),
)
: Wrap(
alignment: WrapAlignment.center,
spacing: 10,
children: [
for (var i = 0; i < _pin.length; i++)
Container(
width: 12,
height: 12,
decoration: const BoxDecoration(
color: AppColors.primary,
shape: BoxShape.circle,
),
),
],
),
),
if (_error != null) ...[
const SizedBox(height: AppSpacing.sm),
Text(
_error!,
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 12.5,
color: AppColors.danger,
fontWeight: FontWeight.w600,
),
),
],
const SizedBox(height: AppSpacing.lg),
NumericKeypad(
onKey: _key,
onBackspace: _backspace,
onClear: _clear,
onSubmit: _checking ? null : _submit,
submitLabel: _checking ? 'Checking…' : 'Approve',
),
],
),
),
),
);
}
}