login_otp.dart 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import 'package:flutter/material.dart';
  2. import 'package:flutter_riverpod/flutter_riverpod.dart';
  3. import 'package:gap/gap.dart';
  4. import 'package:go_router/go_router.dart';
  5. import 'package:supabase_flutter/supabase_flutter.dart';
  6. import 'package:tp5/core/core.dart';
  7. class LoginOtp extends ConsumerStatefulWidget {
  8. const LoginOtp({super.key});
  9. @override
  10. ConsumerState<LoginOtp> createState() => _LoginOtpState();
  11. }
  12. class _LoginOtpState extends ConsumerState<LoginOtp> {
  13. final TextEditingController _emailController = TextEditingController();
  14. final TextEditingController _otpController = TextEditingController();
  15. bool _isOtpSent = false;
  16. bool _isLoading = false;
  17. @override
  18. Widget build(BuildContext context) {
  19. return Scaffold(
  20. appBar: AppBar(
  21. title: Text('Sign In'),
  22. backgroundColor: Colors.blueAccent,
  23. ),
  24. body: SingleChildScrollView(
  25. child: Padding(
  26. padding: const EdgeInsets.all(16.0),
  27. child: Column(
  28. mainAxisAlignment: MainAxisAlignment.center,
  29. crossAxisAlignment: CrossAxisAlignment.stretch,
  30. children: [
  31. SizedBox(height: 50),
  32. Image(
  33. image: AssetImage('assets/logo.png'),
  34. width: 100,
  35. height: 100,
  36. ),
  37. Gap(20),
  38. Text(
  39. 'Welcome to TAR Pilot v5',
  40. style: TextStyle(
  41. fontSize: 24,
  42. fontWeight: FontWeight.bold,
  43. color: Colors.blueAccent,
  44. ),
  45. textAlign: TextAlign.center,
  46. ),
  47. SizedBox(height: 20),
  48. Text(
  49. 'Please enter your email to sign in.',
  50. style: TextStyle(
  51. fontSize: 16,
  52. color: Colors.grey[700],
  53. ),
  54. textAlign: TextAlign.center,
  55. ),
  56. SizedBox(height: 40),
  57. TextField(
  58. controller: _emailController,
  59. decoration: InputDecoration(
  60. labelText: 'Email',
  61. hintText: 'Enter your email',
  62. border: OutlineInputBorder(
  63. borderRadius: BorderRadius.circular(10),
  64. ),
  65. prefixIcon: Icon(Icons.email),
  66. ),
  67. keyboardType: TextInputType.emailAddress,
  68. ),
  69. SizedBox(height: 20),
  70. if (_isOtpSent) ...[
  71. TextField(
  72. controller: _otpController,
  73. decoration: InputDecoration(
  74. labelText: 'OTP',
  75. hintText: 'Enter OTP sent to your email',
  76. border: OutlineInputBorder(
  77. borderRadius: BorderRadius.circular(10),
  78. ),
  79. prefixIcon: Icon(Icons.lock_outline),
  80. ),
  81. keyboardType: TextInputType.number,
  82. ),
  83. SizedBox(height: 20),
  84. ],
  85. ElevatedButton(
  86. onPressed: _isLoading
  87. ? null
  88. : () async {
  89. final email = _emailController.text;
  90. setState(() => _isLoading = true);
  91. try {
  92. if (!_isOtpSent) {
  93. await Supabase.instance.client.auth.signInWithOtp(
  94. email: email,
  95. shouldCreateUser: false,
  96. emailRedirectTo:
  97. 'com.example.tp5://magic-link');
  98. setState(() => _isOtpSent = true);
  99. } else {
  100. final response =
  101. await Supabase.instance.client.auth.verifyOTP(
  102. email: email,
  103. token: _otpController.text,
  104. type: OtpType.email,
  105. );
  106. if (response.session == null) {
  107. _otpController.clear();
  108. ScaffoldMessenger.of(context).showSnackBar(
  109. const SnackBar(
  110. content:
  111. Text('Invalid OTP. Please try again.'),
  112. backgroundColor: Colors.red,
  113. ),
  114. );
  115. } else {
  116. //logged in
  117. print(response.session?.accessToken);
  118. context.go("/home");
  119. }
  120. }
  121. } catch (e) {
  122. if (e is AuthApiException && !_isOtpSent) {
  123. //signup
  124. print("loginotp: new signup");
  125. setState(() => _isLoading = true);
  126. await Supabase.instance.client.auth.signInWithOtp(
  127. email: email,
  128. shouldCreateUser: true,
  129. emailRedirectTo:
  130. 'com.example.tp5://auth/magic-link');
  131. setState(() => _isOtpSent = true);
  132. //go profile after new registration
  133. context.go('/home');
  134. } else {
  135. //error
  136. _otpController.clear();
  137. context.showError('Error: ${e.toString()}');
  138. }
  139. } finally {
  140. if (mounted) {
  141. setState(() => _isLoading = false);
  142. }
  143. }
  144. },
  145. style: ElevatedButton.styleFrom(
  146. padding: EdgeInsets.symmetric(vertical: 15),
  147. backgroundColor: Colors.blueAccent,
  148. shape: RoundedRectangleBorder(
  149. borderRadius: BorderRadius.circular(10),
  150. ),
  151. ),
  152. child: _isLoading
  153. ? const SizedBox(
  154. height: 20,
  155. width: 20,
  156. child: CircularProgressIndicator(
  157. color: Colors.white,
  158. strokeWidth: 2,
  159. ),
  160. )
  161. : Text(
  162. _isOtpSent ? 'Verify OTP' : 'Send OTP',
  163. style: TextStyle(fontSize: 18),
  164. ),
  165. ),
  166. SizedBox(height: 20),
  167. if (_isOtpSent)
  168. TextButton(
  169. onPressed: () {
  170. // Handle use another email
  171. _emailController.clear();
  172. setState(() => _isOtpSent = false);
  173. },
  174. child: Text(
  175. 'Use another mail?',
  176. style: TextStyle(
  177. color: Colors.blueAccent,
  178. fontSize: 16,
  179. ),
  180. ),
  181. ),
  182. ],
  183. ),
  184. ),
  185. ),
  186. );
  187. }
  188. @override
  189. void dispose() {
  190. _emailController.dispose();
  191. _otpController.dispose();
  192. super.dispose();
  193. }
  194. }