This commit is contained in:
efrilm 2025-09-18 09:03:50 +07:00
parent 0ea1a6fa56
commit 8e35582f93
4 changed files with 213 additions and 122 deletions

View File

@ -4,5 +4,5 @@ class ApiPath {
static String verify = '/api/v1/customer-auth/register/verify-otp'; static String verify = '/api/v1/customer-auth/register/verify-otp';
static String setPassword = '/api/v1/customer-auth/register/set-password'; static String setPassword = '/api/v1/customer-auth/register/set-password';
static String login = '/api/v1/customer-auth/login'; static String login = '/api/v1/customer-auth/login';
static String resend = '/api/v1/customer-auth/register/resend-otp'; static String resend = '/api/v1/customer-auth/resend-otp';
} }

View File

@ -2,17 +2,45 @@ import 'dart:async';
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../../application/auth/resend_form/resend_form_bloc.dart';
import '../../../../application/auth/verify_form/verify_form_bloc.dart';
import '../../../../common/theme/theme.dart'; import '../../../../common/theme/theme.dart';
import '../../../../domain/auth/auth.dart';
import '../../../../injection.dart';
import '../../../components/toast/flushbar.dart';
import '../../../router/app_router.gr.dart'; import '../../../router/app_router.gr.dart';
@RoutePage() @RoutePage()
class OtpPage extends StatefulWidget { class OtpPage extends StatefulWidget implements AutoRouteWrapper {
final String registrationToken; final String registrationToken;
const OtpPage({super.key, required this.registrationToken}); final String phoneNumber;
const OtpPage({
super.key,
required this.registrationToken,
required this.phoneNumber,
});
@override @override
State<OtpPage> createState() => _OtpPageState(); State<OtpPage> createState() => _OtpPageState();
@override
Widget wrappedRoute(BuildContext context) => MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => getIt<VerifyFormBloc>()
..add(VerifyFormEvent.registrationTokenChanged(registrationToken)),
),
BlocProvider(
create: (context) => getIt<ResendFormBloc>()
..add(ResendFormEvent.phoneNumberChanged(phoneNumber))
..add(ResendFormEvent.purposeChanged('registration')),
),
],
child: this,
);
} }
class _OtpPageState extends State<OtpPage> { class _OtpPageState extends State<OtpPage> {
@ -54,6 +82,7 @@ class _OtpPageState extends State<OtpPage> {
}); });
_startTimer(); _startTimer();
// Add your resend logic here // Add your resend logic here
context.read<ResendFormBloc>().add(ResendFormEvent.submitted());
} }
String _formatTime(int seconds) { String _formatTime(int seconds) {
@ -83,7 +112,9 @@ class _OtpPageState extends State<OtpPage> {
void _verifyCode() { void _verifyCode() {
String code = _controllers.map((controller) => controller.text).join(); String code = _controllers.map((controller) => controller.text).join();
if (code.length == 6) { if (code.length == 6) {
context.router.push(CreatePasswordRoute()); // context.router.push(CreatePasswordRoute());
context.read<VerifyFormBloc>().add(VerifyFormEvent.otpCodeChanged(code));
context.read<VerifyFormBloc>().add(VerifyFormEvent.submitted());
} }
} }
@ -101,135 +132,178 @@ class _OtpPageState extends State<OtpPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return MultiBlocListener(
appBar: AppBar(title: Text('Verifikasi')), listeners: [
body: Padding( BlocListener<VerifyFormBloc, VerifyFormState>(
padding: const EdgeInsets.symmetric(horizontal: 20.0), listenWhen: (p, c) =>
child: Column( p.failureOrVerifyOption != c.failureOrVerifyOption,
crossAxisAlignment: CrossAxisAlignment.start, listener: (context, state) {
children: [ state.failureOrVerifyOption.fold(
const SizedBox(height: 20), () {},
(either) => either.fold(
// Title (f) => AppFlushbar.showAuthFailureToast(context, f),
Text( (data) {
'Masukan Kode Verifikasi', AppFlushbar.showSuccess(context, data.message);
style: AppStyle.xl.copyWith( Future.delayed(Duration(milliseconds: 1000), () {
fontWeight: FontWeight.w600, context.router.push(CreatePasswordRoute());
color: AppColor.textPrimary, });
},
), ),
), );
},
),
BlocListener<ResendFormBloc, ResendFormState>(
listenWhen: (p, c) =>
p.failureOrResendOption != c.failureOrResendOption,
listener: (context, state) {
state.failureOrResendOption.fold(
() {},
(either) => either.fold(
(f) => AppFlushbar.showAuthFailureToast(context, f),
(data) {
if (data.status.isSuccess) {
AppFlushbar.showSuccess(context, data.message);
} else {
AppFlushbar.showError(context, data.message);
}
},
),
);
},
),
],
child: Scaffold(
appBar: AppBar(title: Text('Verifikasi')),
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 20),
const SizedBox(height: 12), // Title
Text(
'Masukan Kode Verifikasi',
style: AppStyle.xl.copyWith(
fontWeight: FontWeight.w600,
color: AppColor.textPrimary,
),
),
// Description const SizedBox(height: 12),
RichText(
text: TextSpan( // Description
RichText(
text: TextSpan(
children: [
TextSpan(
text: 'Kami telah mengirimkan kode verifikasi melalui ',
style: AppStyle.sm.copyWith(
color: AppColor.textSecondary,
height: 1.4,
),
),
TextSpan(
text: 'Whatsapp',
style: AppStyle.sm.copyWith(
color: AppColor.success,
fontWeight: FontWeight.w500,
height: 1.4,
),
),
TextSpan(
text: ' ke ',
style: AppStyle.sm.copyWith(
color: AppColor.textSecondary,
height: 1.4,
),
),
TextSpan(
text: '+${widget.phoneNumber}',
style: AppStyle.sm.copyWith(
color: AppColor.textPrimary,
fontWeight: FontWeight.w500,
height: 1.4,
),
),
],
),
),
const SizedBox(height: 6),
// Hidden text fields for input
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: List.generate(6, (index) {
return Expanded(
child: Padding(
padding: EdgeInsets.only(right: index == 5 ? 0 : 8.0),
child: TextFormField(
controller: _controllers[index],
focusNode: _focusNodes[index],
keyboardType: TextInputType.number,
maxLength: 1,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly,
],
decoration: InputDecoration(counterText: ''),
textAlign: TextAlign.center,
style: AppStyle.lg.copyWith(
color: AppColor.primary,
fontWeight: FontWeight.w600,
),
cursorColor: AppColor.primary,
onChanged: (value) {
setState(() {});
_onCodeChanged(value, index);
},
),
),
);
}),
),
const SizedBox(height: 40),
// Timer and Resend Section
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
TextSpan( Text(
text: 'Kami telah mengirimkan kode verifikasi melalui ', 'Mohon tunggu untuk kirim ulang kode ',
style: AppStyle.sm.copyWith( style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
),
Text(
_formatTime(_secondsRemaining),
style: AppStyle.xs.copyWith(
color: AppColor.textSecondary, color: AppColor.textSecondary,
height: 1.4,
),
),
TextSpan(
text: 'Whatsapp',
style: AppStyle.sm.copyWith(
color: AppColor.success,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
height: 1.4,
),
),
TextSpan(
text: ' ke ',
style: AppStyle.sm.copyWith(
color: AppColor.textSecondary,
height: 1.4,
),
),
TextSpan(
text: '+6288976680234',
style: AppStyle.sm.copyWith(
color: AppColor.textPrimary,
fontWeight: FontWeight.w500,
height: 1.4,
), ),
), ),
], ],
), ),
),
const SizedBox(height: 6), if (_canResend) ...[
const SizedBox(height: 12),
// Hidden text fields for input Center(
Row( child: TextButton(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, onPressed: _resendCode,
children: List.generate(6, (index) { child: Text(
return Expanded( 'Kirim Ulang Kode',
child: Padding( style: AppStyle.sm.copyWith(
padding: EdgeInsets.only(right: index == 5 ? 0 : 8.0), color: AppColor.success,
child: TextFormField( fontWeight: FontWeight.w500,
controller: _controllers[index],
focusNode: _focusNodes[index],
keyboardType: TextInputType.number,
maxLength: 1,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration(counterText: ''),
textAlign: TextAlign.center,
style: AppStyle.lg.copyWith(
color: AppColor.primary,
fontWeight: FontWeight.w600,
), ),
cursorColor: AppColor.primary,
onChanged: (value) {
setState(() {});
_onCodeChanged(value, index);
},
), ),
), ),
);
}),
),
const SizedBox(height: 40),
// Timer and Resend Section
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'Mohon tunggu untuk kirim ulang kode ',
style: AppStyle.xs.copyWith(color: AppColor.textSecondary),
),
Text(
_formatTime(_secondsRemaining),
style: AppStyle.xs.copyWith(
color: AppColor.textSecondary,
fontWeight: FontWeight.w500,
),
), ),
], ],
),
if (_canResend) ...[ const SizedBox(height: 24),
const SizedBox(height: 12),
Center(
child: TextButton(
onPressed: _resendCode,
child: Text(
'Kirim Ulang Kode',
style: AppStyle.sm.copyWith(
color: AppColor.success,
fontWeight: FontWeight.w500,
),
),
),
),
], ],
),
const SizedBox(height: 24),
],
), ),
), ),
); );

View File

@ -29,7 +29,10 @@ class RegisterPage extends StatelessWidget implements AutoRouteWrapper {
AppFlushbar.showSuccess(context, data.message); AppFlushbar.showSuccess(context, data.message);
Future.delayed(Duration(milliseconds: 1000), () { Future.delayed(Duration(milliseconds: 1000), () {
context.router.push( context.router.push(
OtpRoute(registrationToken: data.registrationToken), OtpRoute(
registrationToken: data.registrationToken,
phoneNumber: phoneNumber,
),
); );
}); });
}, },

View File

@ -420,10 +420,15 @@ class OtpRoute extends _i33.PageRouteInfo<OtpRouteArgs> {
OtpRoute({ OtpRoute({
_i34.Key? key, _i34.Key? key,
required String registrationToken, required String registrationToken,
required String phoneNumber,
List<_i33.PageRouteInfo>? children, List<_i33.PageRouteInfo>? children,
}) : super( }) : super(
OtpRoute.name, OtpRoute.name,
args: OtpRouteArgs(key: key, registrationToken: registrationToken), args: OtpRouteArgs(
key: key,
registrationToken: registrationToken,
phoneNumber: phoneNumber,
),
initialChildren: children, initialChildren: children,
); );
@ -433,24 +438,33 @@ class OtpRoute extends _i33.PageRouteInfo<OtpRouteArgs> {
name, name,
builder: (data) { builder: (data) {
final args = data.argsAs<OtpRouteArgs>(); final args = data.argsAs<OtpRouteArgs>();
return _i20.OtpPage( return _i33.WrappedRoute(
key: args.key, child: _i20.OtpPage(
registrationToken: args.registrationToken, key: args.key,
registrationToken: args.registrationToken,
phoneNumber: args.phoneNumber,
),
); );
}, },
); );
} }
class OtpRouteArgs { class OtpRouteArgs {
const OtpRouteArgs({this.key, required this.registrationToken}); const OtpRouteArgs({
this.key,
required this.registrationToken,
required this.phoneNumber,
});
final _i34.Key? key; final _i34.Key? key;
final String registrationToken; final String registrationToken;
final String phoneNumber;
@override @override
String toString() { String toString() {
return 'OtpRouteArgs{key: $key, registrationToken: $registrationToken}'; return 'OtpRouteArgs{key: $key, registrationToken: $registrationToken, phoneNumber: $phoneNumber}';
} }
} }