import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:descope/descope.dart'; import 'package:go_router/go_router.dart'; class LoginScreen extends ConsumerStatefulWidget { const LoginScreen({super.key}); @override ConsumerState createState() => _LoginScreenState(); } class _LoginScreenState extends ConsumerState with SingleTickerProviderStateMixin { late TabController _tabController; final TextEditingController _emailController = TextEditingController(); final TextEditingController _passwordController = TextEditingController(); final TextEditingController _phoneController = TextEditingController(); @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); } @override void dispose() { _tabController.dispose(); _emailController.dispose(); _passwordController.dispose(); _phoneController.dispose(); super.dispose(); } Future _handleEmailLogin() async { final email = _emailController.text.trim(); if (email.isEmpty) return; // Determine if it's Password or Enchanted Link flow // For this PoC, we'll try Enchanted Link as primary for 'Email' tab per requirements, // but the UI has a password field. Let's support both based on input. // However, PRD says Primary is Email/Password. final password = _passwordController.text; if (password.isNotEmpty) { // Email + Password Flow try { final authResponse = await Descope.auth.password.signIn( loginId: email, password: password, ); final session = DescopeSession.fromAuthenticationResponse(authResponse); Descope.sessionManager.manageSession(session); if (mounted) context.go('/dashboard'); } catch (e) { _showError("Email/Password Login Failed: $e"); } } else { // Enchanted Link Flow (Passwordless) try { // Start Enchanted Link final response = await Descope.auth.enchantedLink.signUpOrIn( loginId: email, uri: "baronsso://auth", // Deep link for the 'Clicked' device ); // Show Polling Dialog if (mounted) { showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: const Text("Check your Email"), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text("We sent an email to $email"), const SizedBox(height: 16), const LinearProgressIndicator(), const SizedBox(height: 16), Text("Link: ${response.linkId}"), // Display for debug/PoC ], ), ), ); // Poll for completion final authResponse = await Descope.auth.enchantedLink.poll( response.pendingRef, ); final session = DescopeSession.fromAuthenticationResponse( authResponse, ); Descope.sessionManager.manageSession(session); if (mounted) { Navigator.of(context).pop(); // Close Dialog context.go('/dashboard'); } } } catch (e) { if (mounted) Navigator.of(context).pop(); // Close dialog if open _showError("Enchanted Link Failed: $e"); } } } Future _handleSmsLogin() async { final phone = _phoneController.text.trim(); if (phone.isEmpty) return; try { // Enchanted Link via SMS (Polling) // Note: This assumes Descope project is configured to send SMS for this loginId final response = await Descope.auth.enchantedLink.signUpOrIn( loginId: phone, uri: "baronsso://auth", // Link for the device that receives SMS ); if (mounted) { showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: const Text("Check your Messages"), content: Column( mainAxisSize: MainAxisSize.min, children: [ Text("We sent a message to $phone"), const SizedBox(height: 16), const LinearProgressIndicator(), const SizedBox(height: 16), // Text("Link: ${response.linkId}"), // Debug ], ), ), ); // Poll for completion final authResponse = await Descope.auth.enchantedLink.poll( response.pendingRef, ); final session = DescopeSession.fromAuthenticationResponse(authResponse); Descope.sessionManager.manageSession(session); if (mounted) { Navigator.of(context).pop(); // Close Dialog context.go('/dashboard'); } } } catch (e) { if (mounted && Navigator.canPop(context)) Navigator.of(context).pop(); _showError("SMS Enchanted Link Failed: $e"); } } void _showError(String message) { if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(message), backgroundColor: Colors.red), ); } @override Widget build(BuildContext context) { return Scaffold( body: Center( child: Container( constraints: const BoxConstraints(maxWidth: 400), padding: const EdgeInsets.all(24), child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( "Baron SSO", style: GoogleFonts.outfit( fontSize: 32, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), const SizedBox(height: 40), // Tab Bar TabBar( controller: _tabController, tabs: const [ Tab(text: "Email"), Tab(text: "Phone (SMS)"), ], ), const SizedBox(height: 24), // Tab View Content SizedBox( height: 300, // Slightly increased height for content child: TabBarView( controller: _tabController, children: [ // Email/Password Form Column( children: [ TextField( controller: _emailController, decoration: const InputDecoration( labelText: "Email", border: OutlineInputBorder(), prefixIcon: Icon(Icons.email_outlined), ), ), const SizedBox(height: 16), TextField( controller: _passwordController, obscureText: true, decoration: const InputDecoration( labelText: "Password", border: OutlineInputBorder(), prefixIcon: Icon(Icons.lock_outline), ), ), const SizedBox(height: 24), FilledButton( onPressed: _handleEmailLogin, style: FilledButton.styleFrom( minimumSize: const Size.fromHeight(50), ), child: const Text("Sign In"), ), ], ), // Phone/SMS Form Column( children: [ TextField( controller: _phoneController, decoration: const InputDecoration( labelText: "Phone Number", hintText: "+82 10-1234-5678", border: OutlineInputBorder(), prefixIcon: Icon(Icons.phone_android), ), ), const SizedBox(height: 24), FilledButton( onPressed: _handleSmsLogin, style: FilledButton.styleFrom( minimumSize: const Size.fromHeight(50), ), child: const Text("Send Login Link"), ), ], ), ], ), ), ], ), ), ), ); } }