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 requireAdminPin( BuildContext context, WidgetRef ref, { required String reason, }) async { final ok = await showDialog( 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 _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', ), ], ), ), ), ); } }