import "package:flutter/material.dart"; import "package:flutter/services.dart"; import "package:provider/provider.dart"; import "../app/app_router.dart"; import "../app/app_state.dart"; import "../models/cart.dart"; import "../services/api.dart"; import "../services/auth_storage.dart"; enum LoginStep { phone, otp } class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @override State createState() => _LoginScreenState(); } class _LoginScreenState extends State { LoginStep _currentStep = LoginStep.phone; final _phoneController = TextEditingController(); final _otpController = TextEditingController(); String _uuid = ""; String _phone = ""; bool _isLoading = false; String? _errorMessage; @override void dispose() { _phoneController.dispose(); _otpController.dispose(); super.dispose(); } String _formatPhoneNumber(String input) { final digits = input.replaceAll(RegExp(r'[^\d]'), ''); if (digits.length == 11 && digits.startsWith('1')) { return digits.substring(1); } return digits; } Future _handleSendOtp() async { final phone = _formatPhoneNumber(_phoneController.text); if (phone.length != 10) { setState(() { _errorMessage = "Please enter a valid 10-digit phone number"; }); return; } setState(() { _isLoading = true; _errorMessage = null; }); try { final response = await Api.sendLoginOtp(phone: phone); if (!mounted) return; if (response.uuid.isEmpty) { setState(() { _errorMessage = "Server error - please try again"; _isLoading = false; }); return; } setState(() { _uuid = response.uuid; _phone = phone; _currentStep = LoginStep.otp; _isLoading = false; }); } catch (e) { if (!mounted) return; setState(() { _errorMessage = e.toString().replaceFirst("StateError: ", ""); _isLoading = false; }); } } Future _handleVerifyOtp() async { if (_uuid.isEmpty) { setState(() { _errorMessage = "Session expired. Please go back and try again."; }); return; } final otp = _otpController.text.trim(); if (otp.length != 6) { setState(() { _errorMessage = "Please enter the 6-digit code"; }); return; } setState(() { _isLoading = true; _errorMessage = null; }); try { final response = await Api.verifyLoginOtp(uuid: _uuid, otp: otp); if (!mounted) return; // Save credentials for persistent login await AuthStorage.saveAuth( userId: response.userId, token: response.token, ); final appState = context.read(); appState.setUserId(response.userId); // Show success ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text( "Welcome back${response.userFirstName.isNotEmpty ? ', ${response.userFirstName}' : ''}!", style: const TextStyle(color: Colors.black), ), backgroundColor: const Color(0xFF90EE90), behavior: SnackBarBehavior.floating, margin: const EdgeInsets.only(bottom: 80, left: 16, right: 16), ), ); // Check for existing cart ActiveCartInfo? existingCart; try { existingCart = await Api.getActiveCart(userId: response.userId); if (existingCart != null && !existingCart.hasItems) { existingCart = null; } } catch (e) { // Ignore - treat as no cart } if (!mounted) return; if (existingCart != null) { // Show continue or start fresh dialog _showExistingCartDialog(existingCart); } else { // No existing cart - just pop back if (Navigator.of(context).canPop()) { Navigator.of(context).pop(); } else { Navigator.of(context).pushReplacementNamed(AppRoutes.restaurantSelect); } } } catch (e) { if (!mounted) return; setState(() { _errorMessage = e.toString().replaceFirst("StateError: ", ""); _isLoading = false; }); } } void _showExistingCartDialog(ActiveCartInfo cart) { showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: const Text("Existing Order Found"), content: Text( "You have ${cart.itemCount} item${cart.itemCount == 1 ? '' : 's'} in your cart at ${cart.businessName}.\n\nWould you like to continue that order or start fresh?", ), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(); // Start fresh - go to restaurant select Navigator.of(this.context).pushReplacementNamed(AppRoutes.restaurantSelect); }, child: const Text("Start Fresh"), ), ElevatedButton( onPressed: () async { Navigator.of(context).pop(); // Continue existing order - load cart and go to menu final appState = this.context.read(); appState.setBusinessAndServicePoint( cart.businessId, cart.servicePointId, businessName: cart.businessName, servicePointName: cart.servicePointName, ); appState.setCartOrder( orderId: cart.orderId, orderUuid: cart.orderUuid, itemCount: cart.itemCount, ); Navigator.of(this.context).pushReplacementNamed(AppRoutes.menuBrowse); }, child: const Text("Continue Order"), ), ], ), ); } Future _handleResendOtp() async { setState(() { _isLoading = true; _errorMessage = null; }); try { final response = await Api.sendLoginOtp(phone: _phone); if (!mounted) return; setState(() { _uuid = response.uuid; _isLoading = false; }); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text("New code sent!", style: TextStyle(color: Colors.black)), backgroundColor: const Color(0xFF90EE90), behavior: SnackBarBehavior.floating, margin: const EdgeInsets.only(bottom: 80, left: 16, right: 16), ), ); } catch (e) { if (!mounted) return; setState(() { _errorMessage = e.toString().replaceFirst("StateError: ", ""); _isLoading = false; }); } } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(_currentStep == LoginStep.phone ? "Login" : "Verify Phone"), ), body: Center( child: SingleChildScrollView( padding: const EdgeInsets.all(24), child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: 400), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (_currentStep == LoginStep.phone) _buildPhoneStep(), if (_currentStep == LoginStep.otp) _buildOtpStep(), // Error message if (_errorMessage != null) ...[ const SizedBox(height: 16), _buildErrorMessage(), ], ], ), ), ), ), ); } Widget _buildPhoneStep() { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ const Text( "PAYFRIT", textAlign: TextAlign.center, style: TextStyle( fontSize: 32, fontWeight: FontWeight.bold, letterSpacing: 2, ), ), const SizedBox(height: 8), const Text( "Enter your phone number to login", textAlign: TextAlign.center, style: TextStyle( fontSize: 16, color: Colors.grey, ), ), const SizedBox(height: 32), TextFormField( controller: _phoneController, decoration: InputDecoration( labelText: "Phone Number", hintText: "(555) 123-4567", hintStyle: TextStyle(color: Colors.grey.shade400), border: const OutlineInputBorder(), prefixIcon: const Icon(Icons.phone), prefixText: "+1 ", ), keyboardType: TextInputType.phone, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(10), ], enabled: !_isLoading, onFieldSubmitted: (_) => _handleSendOtp(), ), const SizedBox(height: 24), FilledButton( onPressed: _isLoading ? null : _handleSendOtp, child: _isLoading ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : const Text("Send Login Code"), ), const SizedBox(height: 16), TextButton( onPressed: _isLoading ? null : () { Navigator.of(context).pushReplacementNamed(AppRoutes.signup); }, child: const Text("Don't have an account? Sign Up"), ), ], ); } Widget _buildOtpStep() { final formattedPhone = _phone.length == 10 ? "(${_phone.substring(0, 3)}) ${_phone.substring(3, 6)}-${_phone.substring(6)}" : _phone; return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Icon( Icons.sms, size: 64, color: Theme.of(context).colorScheme.primary, ), const SizedBox(height: 16), Text( "We sent a code to", textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 4), Text( formattedPhone, textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 24), TextFormField( controller: _otpController, decoration: InputDecoration( labelText: "Login Code", hintText: "123456", hintStyle: TextStyle(color: Colors.grey.shade400), border: const OutlineInputBorder(), prefixIcon: const Icon(Icons.lock), ), keyboardType: TextInputType.number, inputFormatters: [ FilteringTextInputFormatter.digitsOnly, LengthLimitingTextInputFormatter(6), ], textAlign: TextAlign.center, style: const TextStyle( fontSize: 24, letterSpacing: 8, fontWeight: FontWeight.bold, ), enabled: !_isLoading, onFieldSubmitted: (_) => _handleVerifyOtp(), ), const SizedBox(height: 24), FilledButton( onPressed: _isLoading ? null : _handleVerifyOtp, child: _isLoading ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : const Text("Login"), ), const SizedBox(height: 16), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ TextButton( onPressed: _isLoading ? null : _handleResendOtp, child: const Text("Resend Code"), ), const SizedBox(width: 16), TextButton( onPressed: _isLoading ? null : () { setState(() { _currentStep = LoginStep.phone; _otpController.clear(); _errorMessage = null; }); }, child: const Text("Change Number"), ), ], ), ], ); } Widget _buildErrorMessage() { return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: Colors.red.shade50, borderRadius: BorderRadius.circular(8), border: Border.all(color: Colors.red.shade300), ), child: Row( children: [ Icon(Icons.error_outline, color: Colors.red.shade700), const SizedBox(width: 12), Expanded( child: Text( _errorMessage!, style: TextStyle(color: Colors.red.shade900), ), ), ], ), ); } }