143 lines
4.5 KiB
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,
|
|
],
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|