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 "../services/api.dart"; import "../services/auth_storage.dart"; enum SignupStep { phone, otp, profile } class SignupScreen extends StatefulWidget { const SignupScreen({super.key}); @override State createState() => _SignupScreenState(); } class _SignupScreenState extends State { SignupStep _currentStep = SignupStep.phone; // Phone step final _phoneController = TextEditingController(); // OTP step final _otpController = TextEditingController(); String _uuid = ""; String _phone = ""; // Profile step final _firstNameController = TextEditingController(); final _lastNameController = TextEditingController(); final _emailController = TextEditingController(); bool _isLoading = false; String? _errorMessage; int? _userId; String? _token; @override void dispose() { _phoneController.dispose(); _otpController.dispose(); _firstNameController.dispose(); _lastNameController.dispose(); _emailController.dispose(); super.dispose(); } String _formatPhoneNumber(String input) { // Remove all non-digits final digits = input.replaceAll(RegExp(r'[^\d]'), ''); // Remove leading 1 if 11 digits 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.sendOtp(phone: phone); if (!mounted) return; if (response.uuid.isEmpty) { setState(() { _errorMessage = "Server returned empty UUID - please try again"; _isLoading = false; }); return; } setState(() { _uuid = response.uuid; _phone = phone; _currentStep = SignupStep.otp; _isLoading = false; }); } catch (e) { if (!mounted) return; setState(() { _errorMessage = "Error: ${e.toString().replaceFirst("StateError: ", "")}"; _isLoading = false; }); } } Future _handleVerifyOtp() async { // Validate UUID first if (_uuid.isEmpty) { setState(() { _errorMessage = "Session expired - UUID is empty. Please go back and resend code."; }); 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 { print('[Signup] Calling verifyOtp...'); final response = await Api.verifyOtp(uuid: _uuid, otp: otp); print('[Signup] verifyOtp success: userId=${response.userId}, needsProfile=${response.needsProfile}'); if (!mounted) return; _userId = response.userId; _token = response.token; // Save credentials for persistent login await AuthStorage.saveAuth( userId: response.userId, token: response.token, ); print('[Signup] Auth saved, token set'); if (response.needsProfile) { print('[Signup] Profile needed, going to profile step'); // Go to profile step setState(() { _currentStep = SignupStep.profile; _isLoading = false; }); } else { print('[Signup] Profile complete, finishing signup'); // Profile already complete - go to app _completeSignup(); } } catch (e) { print('[Signup] verifyOtp error: $e'); if (!mounted) return; setState(() { _errorMessage = e.toString().replaceFirst("StateError: ", ""); _isLoading = false; }); } } Future _handleCompleteProfile() async { final firstName = _firstNameController.text.trim(); final lastName = _lastNameController.text.trim(); final email = _emailController.text.trim(); if (firstName.isEmpty) { setState(() => _errorMessage = "First name is required"); return; } if (lastName.isEmpty) { setState(() => _errorMessage = "Last name is required"); return; } if (email.isEmpty || !email.contains("@")) { setState(() => _errorMessage = "Please enter a valid email address"); return; } setState(() { _isLoading = true; _errorMessage = null; }); try { print('[Signup] Calling completeProfile: firstName=$firstName, lastName=$lastName, email=$email'); await Api.completeProfile( firstName: firstName, lastName: lastName, email: email, ); print('[Signup] completeProfile success'); if (!mounted) return; _completeSignup(); } catch (e) { print('[Signup] completeProfile error: $e'); if (!mounted) return; setState(() { _errorMessage = e.toString().replaceFirst("StateError: ", ""); _isLoading = false; }); } } void _completeSignup() { final appState = context.read(); if (_userId != null) { appState.setUserId(_userId!); } // Show success and navigate ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: const Text("Account created! Check your email to confirm.", style: TextStyle(color: Colors.black)), backgroundColor: const Color(0xFF90EE90), behavior: SnackBarBehavior.floating, margin: const EdgeInsets.only(bottom: 80, left: 16, right: 16), ), ); // Navigate to main app if (Navigator.of(context).canPop()) { Navigator.of(context).pop(); } else { Navigator.of(context).pushReplacementNamed(AppRoutes.splash); } } Future _handleResendOtp() async { setState(() { _isLoading = true; _errorMessage = null; }); try { final response = await Api.resendOtp(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(_getStepTitle()), ), 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: [ // Progress indicator _buildProgressIndicator(), const SizedBox(height: 32), // Step content if (_currentStep == SignupStep.phone) _buildPhoneStep(), if (_currentStep == SignupStep.otp) _buildOtpStep(), if (_currentStep == SignupStep.profile) _buildProfileStep(), // Error message if (_errorMessage != null) ...[ const SizedBox(height: 16), _buildErrorMessage(), ], ], ), ), ), ), ); } String _getStepTitle() { switch (_currentStep) { case SignupStep.phone: return "Create Account"; case SignupStep.otp: return "Verify Phone"; case SignupStep.profile: return "Your Info"; } } Widget _buildProgressIndicator() { return Row( children: [ _buildStepDot(0, _currentStep.index >= 0), Expanded(child: _buildStepLine(_currentStep.index >= 1)), _buildStepDot(1, _currentStep.index >= 1), Expanded(child: _buildStepLine(_currentStep.index >= 2)), _buildStepDot(2, _currentStep.index >= 2), ], ); } Widget _buildStepDot(int step, bool isActive) { return Container( width: 32, height: 32, decoration: BoxDecoration( shape: BoxShape.circle, color: isActive ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.surfaceContainerHighest, ), child: Center( child: Text( "${step + 1}", style: TextStyle( color: isActive ? Theme.of(context).colorScheme.onPrimary : Theme.of(context).colorScheme.onSurfaceVariant, fontWeight: FontWeight.bold, ), ), ), ); } Widget _buildStepLine(bool isActive) { return Container( height: 2, color: isActive ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.surfaceContainerHighest, ); } 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 get started", textAlign: TextAlign.center, style: TextStyle( fontSize: 16, color: Colors.grey, ), ), const SizedBox(height: 32), TextFormField( controller: _phoneController, decoration: const InputDecoration( labelText: "Phone Number", hintText: "(555) 123-4567", border: OutlineInputBorder(), prefixIcon: 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 Verification Code"), ), const SizedBox(height: 16), TextButton( onPressed: _isLoading ? null : () { Navigator.of(context).pushReplacementNamed(AppRoutes.login); }, child: const Text("Already have an account? Login"), ), ], ); } 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: const InputDecoration( labelText: "Verification Code", hintText: "123456", border: OutlineInputBorder(), prefixIcon: 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("Verify"), ), 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 = SignupStep.phone; _otpController.clear(); _errorMessage = null; }); }, child: const Text("Change Number"), ), ], ), ], ); } Widget _buildProfileStep() { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Icon( Icons.person_add, size: 64, color: Theme.of(context).colorScheme.primary, ), const SizedBox(height: 16), Text( "Almost done!", textAlign: TextAlign.center, style: Theme.of(context).textTheme.titleLarge?.copyWith( fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), Text( "Tell us a bit about yourself", textAlign: TextAlign.center, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Colors.grey, ), ), const SizedBox(height: 32), TextFormField( controller: _firstNameController, decoration: const InputDecoration( labelText: "First Name", border: OutlineInputBorder(), prefixIcon: Icon(Icons.person), ), textCapitalization: TextCapitalization.words, textInputAction: TextInputAction.next, enabled: !_isLoading, ), const SizedBox(height: 16), TextFormField( controller: _lastNameController, decoration: const InputDecoration( labelText: "Last Name", border: OutlineInputBorder(), prefixIcon: Icon(Icons.person_outline), ), textCapitalization: TextCapitalization.words, textInputAction: TextInputAction.next, enabled: !_isLoading, ), const SizedBox(height: 16), TextFormField( controller: _emailController, decoration: const InputDecoration( labelText: "Email Address", border: OutlineInputBorder(), prefixIcon: Icon(Icons.email), helperText: "We'll send a confirmation email", ), keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.done, enabled: !_isLoading, onFieldSubmitted: (_) => _handleCompleteProfile(), ), const SizedBox(height: 24), FilledButton( onPressed: _isLoading ? null : _handleCompleteProfile, child: _isLoading ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : const Text("Complete Sign Up"), ), ], ); } 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), ), ), ], ), ); } }