import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../../app/providers.dart'; import '../../../core/router/app_router.dart'; import '../../../core/theme/app_colors.dart'; import '../../../core/theme/app_dimens.dart'; import '../../../core/utils/extensions.dart'; import '../../../core/utils/formatters.dart'; import '../../../core/utils/validators.dart'; import '../../../core/widgets/glass_card.dart'; import '../../../core/widgets/primary_button.dart'; import '../../../domain/entities/customer.dart'; import '../../pos/providers/cart_controller.dart'; import '../providers/customer_providers.dart'; /// Screen 2 — registers a shopper and drops straight into billing. class CustomerRegistrationScreen extends ConsumerStatefulWidget { const CustomerRegistrationScreen({super.key, this.prefillMobile}); final String? prefillMobile; @override ConsumerState createState() => _CustomerRegistrationScreenState(); } class _CustomerRegistrationScreenState extends ConsumerState { final _formKey = GlobalKey(); late final TextEditingController _mobile; final _name = TextEditingController(); final _email = TextEditingController(); Gender _gender = Gender.unspecified; DateTime? _dob; bool _saving = false; String? _serverError; @override void initState() { super.initState(); _mobile = TextEditingController(text: widget.prefillMobile ?? ''); } @override void dispose() { _mobile.dispose(); _name.dispose(); _email.dispose(); super.dispose(); } Future _save() async { setState(() => _serverError = null); if (!(_formKey.currentState?.validate() ?? false)) return; setState(() => _saving = true); try { final customer = await ref.read(customerRepositoryProvider).create( Customer( id: '', name: _name.text, mobile: _mobile.text, email: _email.text, gender: _gender, dateOfBirth: _dob, ), ); ref.read(cartControllerProvider.notifier).attachCustomer(customer); ref.invalidate(recentCustomersProvider); if (!mounted) return; context.go(AppRoutes.pos); } catch (e) { if (!mounted) return; setState(() { _saving = false; _serverError = e is StateError ? e.message : 'Could not save customer.'; }); } } Future _pickDob() async { final now = DateTime.now(); final picked = await showDatePicker( context: context, initialDate: _dob ?? DateTime(now.year - 25), firstDate: DateTime(now.year - 100), lastDate: now, helpText: 'Date of birth', ); if (picked != null) setState(() => _dob = picked); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: AppColors.background, appBar: AppBar( title: const Text('New Customer'), leading: IconButton( icon: const Icon(Icons.arrow_back_rounded), onPressed: () => context.pop(), ), ), body: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(AppSpacing.xxl), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 640), child: GlassCard( padding: const EdgeInsets.all(AppSpacing.xxxl), radius: AppRadius.xl, shadows: AppColors.shadowMd, child: Form( key: _formKey, autovalidateMode: AutovalidateMode.onUserInteraction, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Register a shopper', style: context.text.headlineSmall), const SizedBox(height: AppSpacing.xs), Text( 'Only the mobile number and name are required.', style: context.text.bodySmall, ), const SizedBox(height: AppSpacing.xxl), _Field( label: 'Mobile Number', required: true, child: TextFormField( controller: _mobile, autofocus: true, keyboardType: TextInputType.phone, maxLength: 10, validator: Validators.mobile, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, ], decoration: const InputDecoration( hintText: '10-digit mobile number', prefixText: '+91 ', counterText: '', prefixIcon: Icon(Icons.phone_outlined), ), ), ), _Field( label: 'Customer Name', required: true, child: TextFormField( controller: _name, textCapitalization: TextCapitalization.words, validator: Validators.name, decoration: const InputDecoration( hintText: 'Full name', prefixIcon: Icon(Icons.person_outline_rounded), ), ), ), _Field( label: 'Email', child: TextFormField( controller: _email, keyboardType: TextInputType.emailAddress, validator: Validators.emailOptional, decoration: const InputDecoration( hintText: 'name@example.com', prefixIcon: Icon(Icons.mail_outline_rounded), ), ), ), _Field( label: 'Gender', child: Wrap( spacing: AppSpacing.sm, children: Gender.values .map((g) => ChoiceChip( label: Text(g.label), selected: _gender == g, onSelected: (_) => setState(() => _gender = g), labelStyle: TextStyle( color: _gender == g ? Colors.white : AppColors.textSecondary, fontWeight: FontWeight.w600, ), )) .toList(), ), ), _Field( label: 'Date of Birth', child: InkWell( onTap: _pickDob, borderRadius: AppRadius.brMd, child: InputDecorator( decoration: const InputDecoration( prefixIcon: Icon(Icons.cake_outlined), ), child: Text( _dob == null ? 'Select a date (optional)' : Formatters.date(_dob!), style: TextStyle( color: _dob == null ? AppColors.textTertiary : AppColors.textPrimary, fontSize: 15, ), ), ), ), ), if (_serverError != null) ...[ const SizedBox(height: AppSpacing.sm), Container( width: double.infinity, padding: const EdgeInsets.all(AppSpacing.md), decoration: BoxDecoration( color: AppColors.dangerSurface, borderRadius: AppRadius.brSm, ), child: Row(children: [ const Icon(Icons.error_outline_rounded, color: AppColors.danger, size: 18), const SizedBox(width: AppSpacing.sm), Expanded( child: Text( _serverError!, style: const TextStyle( color: AppColors.danger, fontSize: 13.5, ), ), ), ]), ), ], const SizedBox(height: AppSpacing.xxl), Row(children: [ Expanded( child: PrimaryButton( label: 'Cancel', tone: ButtonTone.neutral, onPressed: _saving ? null : () => context.pop(), ), ), const SizedBox(width: AppSpacing.md), Expanded( flex: 2, child: PrimaryButton( label: 'Save & Continue', icon: Icons.check_rounded, busy: _saving, onPressed: _save, ), ), ]), ], ), ), ), ), ), ), ); } } class _Field extends StatelessWidget { const _Field({ required this.label, required this.child, this.required = false, }); final String label; final Widget child; final bool required; @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: AppSpacing.lg), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row(children: [ Text(label, style: context.text.labelMedium ?.copyWith(color: AppColors.textSecondary)), if (required) const Text(' *', style: TextStyle(color: AppColors.danger, fontSize: 13)), if (!required) Text(' optional', style: context.text.labelSmall ?.copyWith(color: AppColors.textTertiary)), ]), const SizedBox(height: AppSpacing.sm), child, ], ), ); } }