Roadbound-BRR/lib/pages/auth/verify_email_page.dart

143 lines
4.5 KiB
Dart

import "dart:async";
import "package:bus_running_record/pages/auth/page.dart";
import "package:bus_running_record/provider/supabase_state.dart";
import "package:go_router/go_router.dart";
import "package:provider/provider.dart";
import "package:shadcn_flutter/shadcn_flutter.dart";
class VerifyEmailPage extends StatefulWidget {
static const String routePath = "/verify-email";
static final GoRoute route = GoRoute(
path: routePath,
builder: (context, state) {
final email = state.uri.queryParameters["email"] ?? "";
return VerifyEmailPage(email: email);
},
);
const VerifyEmailPage({required this.email, super.key});
final String email;
@override
State<VerifyEmailPage> createState() => _VerifyEmailPageState();
}
class _VerifyEmailPageState extends State<VerifyEmailPage> {
static const int _tokenLength = 6;
String _token = "";
bool _isSubmitting = false;
String? _error;
Future<void> _verifyToken(String token) async {
if (widget.email.isEmpty) {
setState(() => _error = "Missing email address for verification.");
return;
}
if (token.length != _tokenLength) {
setState(() => _error = "Enter the $_tokenLength-digit token from your email.");
return;
}
setState(() {
_isSubmitting = true;
_error = null;
});
try {
await context.read<SupabaseProvider>().verifySignUpOtp(
email: widget.email,
token: token,
);
if (mounted) context.go("/");
} catch (error, stackTrace) {
debugPrint(
"[VerifyEmailPage] verifyToken failed (${error.runtimeType}): $error",
);
debugPrintStack(stackTrace: stackTrace);
if (mounted) {
setState(() {
_error = error.toString();
});
}
} finally {
if (mounted) {
setState(() => _isSubmitting = false);
}
}
}
@override
Widget build(BuildContext context) {
final topPadding = MediaQuery.of(context).padding.top;
return Scaffold(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 460),
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Gap(topPadding + 16),
Text("You're almost there!").x2Large.semiBold,
const Gap(8),
Text(
"We sent a $_tokenLength-digit token to your email.\n(${widget.email})",
).xSmall.muted.textCenter,
const Gap(14),
InputOTP(
onChanged: (value) {
setState(() {
_token = value.otpToString();
_error = null;
});
},
onSubmitted: (value) {
FocusScope.of(context).unfocus();
unawaited(_verifyToken(value.otpToString()));
},
children: [
InputOTPChild.character(allowDigit: true),
InputOTPChild.character(allowDigit: true),
InputOTPChild.character(allowDigit: true),
InputOTPChild.character(allowDigit: true),
InputOTPChild.character(allowDigit: true),
InputOTPChild.character(allowDigit: true),
],
),
const Gap(20),
Button.primary(
enabled: !_isSubmitting && _token.length == _tokenLength,
onPressed: () => unawaited(_verifyToken(_token)),
alignment: Alignment.center,
child: Text(
_isSubmitting ? "Verifying..." : "Verify token",
),
),
const Gap(8),
Button.text(
onPressed: _isSubmitting
? null
: () => context.go(LoginPage.routePath),
child: const Text("Back to sign in"),
),
if (_error != null) ...[
const Gap(8),
Text(
_error!,
style: TextStyle(
color: Theme.of(context).colorScheme.destructive,
),
).xSmall.textCenter,
],
],
),
),
),
),
);
}
}