diff --git a/.dart_tool/package_config.json b/.dart_tool/package_config.json
index 087bb40..ec4594a 100644
--- a/.dart_tool/package_config.json
+++ b/.dart_tool/package_config.json
@@ -680,7 +680,7 @@
"languageVersion": "3.4"
}
],
- "generated": "2025-05-09T09:48:57.570155Z",
+ "generated": "2025-05-10T18:42:15.590215Z",
"generator": "pub",
"generatorVersion": "3.5.0",
"flutterRoot": "file:///D:/Flutter/flutter_sdk/flutter_3.24.0",
diff --git a/assets/images/il_email.svg b/assets/images/il_email.svg
new file mode 100644
index 0000000..a1b4ab0
--- /dev/null
+++ b/assets/images/il_email.svg
@@ -0,0 +1,143 @@
+
diff --git a/lib/_core/validators/validators.dart b/lib/_core/validators/validators.dart
index 331d31b..adf35c6 100644
--- a/lib/_core/validators/validators.dart
+++ b/lib/_core/validators/validators.dart
@@ -1,3 +1,5 @@
+import 'package:flutter/material.dart';
+
class Validators {
static String? validatorEmail(String? value) {
if (value == null || value.isEmpty) {
@@ -13,6 +15,20 @@ class Validators {
return null;
}
+ static String? Function(String?) validatorConfirmPassword(
+ TextEditingController passwordController,
+ ) {
+ return (String? value) {
+ if (value == null || value.isEmpty) {
+ return 'Password tidak boleh kosong';
+ }
+ if (value != passwordController.text) {
+ return 'Password tidak cocok';
+ }
+ return null;
+ };
+ }
+
static String? validatorName(String? value) {
if (value == null || value.isEmpty) {
return 'Nama Lengkap tidak boleh kosong';
diff --git a/lib/data/repositories/auth_repository_impl.dart b/lib/data/repositories/auth_repository_impl.dart
index b3fb1d5..0618abe 100644
--- a/lib/data/repositories/auth_repository_impl.dart
+++ b/lib/data/repositories/auth_repository_impl.dart
@@ -1,10 +1,10 @@
+import 'dart:developer';
+
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:e_porter/domain/models/user_entity.dart';
import 'package:e_porter/domain/repositories/auth_repository.dart';
import 'package:firebase_auth/firebase_auth.dart';
-import '../../_core/service/logger_service.dart';
-
class AuthException implements Exception {
final String message;
AuthException(this.message);
@@ -24,11 +24,15 @@ class AuthRepositoryImpl implements AuthRepository {
password: password,
);
final user = userCredential.user!;
+
+ await user.reload();
+ if (!user.emailVerified) {
+ await _firebaseAuth.signOut();
+ throw AuthException("email-not-verified");
+ }
+
return UserEntity(uid: user.uid, email: user.email ?? "");
} on FirebaseAuthException catch (e) {
- logger.w("FirebaseAuthException code: ${e.code}");
- logger.w("FirebaseAuthException message: ${e.message}");
-
switch (e.code) {
case 'invalid-email':
throw AuthException("Format email tidak valid.");
@@ -44,6 +48,43 @@ class AuthRepositoryImpl implements AuthRepository {
}
}
+ @override
+ Future registerWithEmailPassword(String email, String password) async {
+ try {
+ UserCredential userCredential = await _firebaseAuth.createUserWithEmailAndPassword(
+ email: email,
+ password: password,
+ );
+ final user = userCredential.user!;
+ await user.sendEmailVerification();
+ await user.updateDisplayName(email);
+
+ return UserEntity(uid: user.uid, email: user.email ?? "");
+ } on FirebaseAuthException catch (e) {
+ log("FirebaseAuthException code: ${e.code}");
+ log("FirebaseAuthException message: ${e.message}");
+
+ throw AuthException(e.code);
+ } catch (e) {
+ throw AuthException(e.toString());
+ }
+ }
+
+ @override
+ Future saveUserData(UserData userData) async {
+ try {
+ await _firestore.collection('users').doc(userData.uid).set(
+ userData.toMap(),
+ SetOptions(merge: true),
+ );
+
+ log("User data berhasil disimpan ke Firestore");
+ } catch (e) {
+ log("Error saving user data: $e");
+ throw AuthException("Gagal menyimpan data pengguna.");
+ }
+ }
+
@override
Future signOut() async {
await _firebaseAuth.signOut();
diff --git a/lib/domain/bindings/auth_binding.dart b/lib/domain/bindings/auth_binding.dart
index 5777106..191537c 100644
--- a/lib/domain/bindings/auth_binding.dart
+++ b/lib/domain/bindings/auth_binding.dart
@@ -11,14 +11,18 @@ class AuthBinding extends Bindings {
final authRepository = AuthRepositoryImpl(firebaseAuth);
final loginUseCase = LoginUseCase(authRepository);
- final getUserRoleUseCase = GetUserRoleUseCase(authRepository);
+ final getUserRoleUseCase = GetUserRoleUseCase(authRepository);
final getUserDataUseCase = GetUserDataUseCase(authRepository);
+ final registerUseCase = RegisterUseCase(authRepository);
+ final saveUserDataUseCase = SaveUserDataUseCase(authRepository);
Get.put(
AuthController(
loginUseCase: loginUseCase,
- getUserRoleUseCase: getUserRoleUseCase,
+ getUserRoleUseCase: getUserRoleUseCase,
getUserDataUseCase: getUserDataUseCase,
+ registerUseCase: registerUseCase,
+ saveUserDataUseCase: saveUserDataUseCase,
),
);
}
diff --git a/lib/domain/models/user_entity.dart b/lib/domain/models/user_entity.dart
index a74e478..6481393 100644
--- a/lib/domain/models/user_entity.dart
+++ b/lib/domain/models/user_entity.dart
@@ -26,17 +26,17 @@ class UserData {
UserData({
required this.uid,
- required this.tipeId,
- required this.noId,
- required this.name,
- required this.email,
- required this.phone,
- required this.birthDate,
- required this.gender,
- required this.work,
- required this.city,
- required this.address,
- required this.role,
+ this.tipeId,
+ this.noId,
+ this.name,
+ this.email,
+ this.phone,
+ this.birthDate,
+ this.gender,
+ this.work,
+ this.city,
+ this.address,
+ this.role,
});
factory UserData.fromMap(Map map) {
@@ -83,7 +83,7 @@ class UserData {
'role': role,
};
}
-
+
UserData copyWith({
String? uid,
String? tipeId,
diff --git a/lib/domain/repositories/auth_repository.dart b/lib/domain/repositories/auth_repository.dart
index ae03d48..f95cacd 100644
--- a/lib/domain/repositories/auth_repository.dart
+++ b/lib/domain/repositories/auth_repository.dart
@@ -3,8 +3,10 @@ import 'package:e_porter/domain/models/user_entity.dart';
abstract class AuthRepository {
Future signInWithEmailPassword(String email, String password);
Future signOut();
-
+
Future getUserRole(String uid);
Future getUserData(String uid);
-}
\ No newline at end of file
+ Future registerWithEmailPassword(String email, String password);
+ Future saveUserData(UserData userData);
+}
diff --git a/lib/domain/usecases/auth_usecase.dart b/lib/domain/usecases/auth_usecase.dart
index 114c810..e1bfa64 100644
--- a/lib/domain/usecases/auth_usecase.dart
+++ b/lib/domain/usecases/auth_usecase.dart
@@ -23,8 +23,28 @@ class GetUserRoleUseCase {
class GetUserDataUseCase {
final AuthRepository authRepository;
GetUserDataUseCase(this.authRepository);
-
+
Future call(String uid) async {
return await authRepository.getUserData(uid);
}
}
+
+class RegisterUseCase {
+ final AuthRepository authRepository;
+
+ RegisterUseCase(this.authRepository);
+
+ Future call(String email, String password) async {
+ return await authRepository.registerWithEmailPassword(email, password);
+ }
+}
+
+class SaveUserDataUseCase {
+ final AuthRepository authRepository;
+
+ SaveUserDataUseCase(this.authRepository);
+
+ Future call(UserData userData) async {
+ return await authRepository.saveUserData(userData);
+ }
+}
diff --git a/lib/presentation/controllers/auth_controller.dart b/lib/presentation/controllers/auth_controller.dart
index 9690aaf..bf0d734 100644
--- a/lib/presentation/controllers/auth_controller.dart
+++ b/lib/presentation/controllers/auth_controller.dart
@@ -1,5 +1,8 @@
+import 'package:e_porter/_core/utils/snackbar/snackbar_helper.dart';
import 'package:e_porter/data/repositories/auth_repository_impl.dart';
+import 'package:e_porter/domain/models/user_entity.dart';
import 'package:e_porter/domain/usecases/auth_usecase.dart';
+import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
@@ -11,6 +14,8 @@ class AuthController extends GetxController {
final LoginUseCase loginUseCase;
final GetUserRoleUseCase getUserRoleUseCase;
final GetUserDataUseCase getUserDataUseCase;
+ final RegisterUseCase registerUseCase;
+ final SaveUserDataUseCase saveUserDataUseCase;
final emailController = TextEditingController();
final passwordController = TextEditingController();
@@ -22,13 +27,15 @@ class AuthController extends GetxController {
required this.loginUseCase,
required this.getUserRoleUseCase,
required this.getUserDataUseCase,
+ required this.registerUseCase,
+ required this.saveUserDataUseCase,
});
Future login({String? roleFromOnboarding}) async {
errorMessage.value = '';
if (emailController.text.isEmpty || passwordController.text.isEmpty) {
- _showErrorSnackbar("Error", "Email/Password tidak boleh kosong");
+ SnackbarHelper.showError("Error", "Email/Password tidak boleh kosong");
return;
}
isLoading.value = true;
@@ -44,7 +51,7 @@ class AuthController extends GetxController {
logger.d("roleFromDB: $roleFromDB, roleFromOnboarding: $roleFromOnboarding, UID: $uid");
if (roleFromDB != null && roleFromOnboarding != null && roleFromDB != roleFromOnboarding) {
- _showErrorSnackbar(
+ SnackbarHelper.showError(
"Role Tidak Sesuai", "Akun ini terdaftar sebagai '$roleFromDB', bukan '$roleFromOnboarding'.");
return;
}
@@ -53,12 +60,12 @@ class AuthController extends GetxController {
final userData = await getUserDataUseCase(uid);
if (userData == null) {
- _showErrorSnackbar("Login Gagal", "Data user tidak ditemukan.");
+ SnackbarHelper.showError("Login Gagal", "Data user tidak ditemukan.");
return;
}
if (userData.role!.toLowerCase() != effectiveRole.toLowerCase()) {
- _showErrorSnackbar(
+ SnackbarHelper.showError(
"Role Tidak Sesuai", "Data user menunjukkan role '${userData.role}', bukan '$effectiveRole'.");
return;
}
@@ -66,21 +73,140 @@ class AuthController extends GetxController {
await PreferencesService.saveUserData(userData);
Get.offAllNamed(Routes.NAVBAR, arguments: effectiveRole);
} on AuthException catch (e) {
- _showErrorSnackbar("Login Gagal", e.message);
+ if (e.message == 'email-not-verified') {
+ SnackbarHelper.showError(
+ "Verifikasi Diperlukan",
+ "Silakan cek email Anda dan klik link verifikasi sebelum login.",
+ );
+ } else {
+ SnackbarHelper.showError(
+ "Login Gagal",
+ "Email atau password anda salah.",
+ );
+ }
} catch (e) {
- _showErrorSnackbar("Terjadi Kesalahan", e.toString());
+ SnackbarHelper.showError("Terjadi Kesalahan", e.toString());
} finally {
isLoading.value = false;
}
}
- void _showErrorSnackbar(String title, String message) {
- Get.snackbar(
- title,
- message,
- snackPosition: SnackPosition.TOP,
- backgroundColor: Colors.red,
- colorText: Colors.white,
- );
+ Future register({
+ required String name,
+ required String email,
+ required String password,
+ required String role,
+ }) async {
+ errorMessage.value = '';
+ isLoading.value = true;
+
+ try {
+ final userEntity = await registerUseCase(
+ email.trim(),
+ password,
+ );
+
+ final userData = UserData(
+ uid: userEntity.uid,
+ tipeId: null,
+ noId: null,
+ name: name.trim(),
+ email: email.trim(),
+ phone: null,
+ birthDate: null,
+ gender: null,
+ work: null,
+ city: null,
+ address: null,
+ role: role,
+ );
+
+ await saveUserDataUseCase(userData);
+
+ SnackbarHelper.showSuccess(
+ "Berhasil",
+ "Akun berhasil dibuat. Silakan cek email Anda untuk verifikasi terlebih dahulu.",
+ );
+
+ Get.offNamed(Routes.VERIFICATION);
+ } on AuthException catch (e) {
+ switch (e.message) {
+ case 'weak-password':
+ SnackbarHelper.showError("Error", "Password terlalu lemah.");
+ break;
+ case 'email-already-in-use':
+ SnackbarHelper.showError("Error", "Email sudah terdaftar.");
+ break;
+ case 'invalid-email':
+ SnackbarHelper.showError("Error", "Format email tidak valid.");
+ break;
+ default:
+ SnackbarHelper.showError("Registrasi Gagal", "Terjadi kesalahan saat registrasi.");
+ }
+ } catch (e) {
+ SnackbarHelper.showError("Terjadi Kesalahan", e.toString());
+ } finally {
+ isLoading.value = false;
+ }
}
+
+ Future resendEmailVerification() async {
+ final user = FirebaseAuth.instance.currentUser;
+ if (user == null) {
+ SnackbarHelper.showError("Error", "User tidak ditemukan.");
+ return;
+ }
+ if (user.emailVerified) {
+ SnackbarHelper.showSuccess("Sudah Terverifikasi", "Email Anda sudah terverifikasi.");
+ return;
+ }
+ try {
+ await user.sendEmailVerification();
+ SnackbarHelper.showSuccess(
+ "Terkirim",
+ "Link verifikasi telah dikirim ulang ke email Anda.",
+ );
+ } on FirebaseException catch (e) {
+ if (e.message?.contains('No AppCheckProvider') == true) {
+ SnackbarHelper.showSuccess(
+ "Terkirim",
+ "Link verifikasi telah dikirim ulang ke email Anda.",
+ );
+ } else {
+ SnackbarHelper.showError(
+ "Gagal",
+ "Gagal mengirim ulang verifikasi. Silakan coba lagi.",
+ );
+ }
+ } catch (e) {
+ SnackbarHelper.showError(
+ "Gagal",
+ "Gagal mengirim ulang verifikasi. Silakan coba lagi.",
+ );
+ }
+ }
+
+ Future completeEmailVerification() async {
+ final user = FirebaseAuth.instance.currentUser;
+ if (user == null) return;
+ await user.reload();
+
+ if (user.emailVerified) {
+ final userData = await getUserDataUseCase(user.uid);
+ if (userData != null) {
+ await PreferencesService.saveUserData(userData);
+ }
+ Get.offAllNamed(Routes.NAVBAR, arguments: userData?.role);
+ }
+ }
+
+ // void _showErrorSnackbar(String title, String message) {
+ // Get.snackbar(
+ // title,
+ // message,
+ // snackPosition: SnackPosition.TOP,
+ // backgroundColor: Colors.red,
+ // colorText: Colors.white,
+ // );
+ // }
}
diff --git a/lib/presentation/screens/auth/pages/login_screen.dart b/lib/presentation/screens/auth/pages/login_screen.dart
index 2336b37..e7dd372 100644
--- a/lib/presentation/screens/auth/pages/login_screen.dart
+++ b/lib/presentation/screens/auth/pages/login_screen.dart
@@ -1,3 +1,5 @@
+import 'dart:developer';
+
import 'package:e_porter/_core/component/button/button_fill.dart';
import 'package:e_porter/_core/constants/colors.dart';
import 'package:e_porter/_core/constants/typography.dart';
@@ -128,7 +130,8 @@ class _LoginScreenState extends State {
firstText: 'Belum punya akun?',
secondText: 'Daftar',
onTab: () {
- Get.toNamed(Routes.REGISTER);
+ log('Role Login: $role');
+ Get.toNamed(Routes.REGISTER, arguments: role);
},
),
),
diff --git a/lib/presentation/screens/auth/pages/register_screen.dart b/lib/presentation/screens/auth/pages/register_screen.dart
index e740bc9..278300e 100644
--- a/lib/presentation/screens/auth/pages/register_screen.dart
+++ b/lib/presentation/screens/auth/pages/register_screen.dart
@@ -1,6 +1,12 @@
+import 'dart:developer';
+
+import 'package:e_porter/_core/utils/formatter/uppercase_helper.dart';
+import 'package:e_porter/_core/validators/validators.dart';
+import 'package:e_porter/presentation/controllers/auth_controller.dart';
import 'package:e_porter/presentation/screens/auth/component/header_text.dart';
import 'package:e_porter/presentation/screens/routes/app_rountes.dart';
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:zoom_tap_animation/zoom_tap_animation.dart';
@@ -19,6 +25,37 @@ class RegisterScreen extends StatefulWidget {
}
class _RegisterScreenState extends State {
+ final String? role = Get.arguments as String;
+ final _formKey = GlobalKey();
+ final TextEditingController _name = TextEditingController();
+ final TextEditingController _email = TextEditingController();
+ final TextEditingController _password = TextEditingController();
+ final TextEditingController _verifPassword = TextEditingController();
+ final AuthController _authController = Get.find();
+
+ void _handleRegister() {
+ if (_formKey.currentState!.validate()) {
+ if (_password.text != _verifPassword.text) {
+ Get.snackbar(
+ 'Error',
+ 'Password tidak cocok',
+ snackPosition: SnackPosition.TOP,
+ backgroundColor: Colors.red,
+ colorText: Colors.white,
+ );
+ return;
+ }
+
+ _authController.register(
+ name: _name.text,
+ email: _email.text,
+ password: _password.text,
+ role: role.toString(),
+ );
+ log('Role Registrasi: $role');
+ }
+ }
+
@override
Widget build(BuildContext context) {
return Scaffold(
@@ -28,72 +65,86 @@ class _RegisterScreenState extends State {
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
child: SingleChildScrollView(
child: Form(
+ key: _formKey,
child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- HeaderText(
- firstText: 'Daftar',
- secondText:
- 'Segera daftarkan diri anda ke aplikasi ini untuk akses penuh ke fitur kami!',
- ),
- SizedBox(height: 50.h),
- Padding(
- padding: EdgeInsets.symmetric(horizontal: 8.w),
- child: TypographyStyles.body(
- 'Nama',
- color: GrayColors.gray800,
- fontWeight: FontWeight.w500,
- ),
- ),
- SizedBox(height: 16.h),
- InputForm(
- hintText: 'Suparjo',
- svgIconPath: 'assets/icons/ic_account.svg',
- ),
- SizedBox(height: 20.h),
- Padding(
- padding: EdgeInsets.symmetric(horizontal: 8.w),
- child: TypographyStyles.body(
- 'Email',
- color: GrayColors.gray800,
- fontWeight: FontWeight.w500,
- ),
- ),
- SizedBox(height: 16.h),
- InputForm(
- hintText: 'example@gmail.com',
- svgIconPath: 'assets/icons/ic_email.svg',
- ),
- SizedBox(height: 20.h),
- Padding(
- padding: EdgeInsets.symmetric(horizontal: 8.w),
- child: TypographyStyles.body(
- 'Password',
- color: GrayColors.gray800,
- fontWeight: FontWeight.w500,
- ),
- ),
- SizedBox(height: 16.h),
- InputPassword(
- hintText: '••••••••••',
- svgIconPath: 'assets/icons/ic_padlock.svg',
- ),
- SizedBox(height: 20.h),
- Padding(
- padding: EdgeInsets.symmetric(horizontal: 8.w),
- child: TypographyStyles.body(
- 'Konfirmasi Password',
- color: GrayColors.gray800,
- fontWeight: FontWeight.w500,
- ),
- ),
- SizedBox(height: 16.h),
- InputPassword(
- hintText: '••••••••••',
- svgIconPath: 'assets/icons/ic_padlock.svg',
- ),
- ],
- )),
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ HeaderText(
+ firstText: 'Daftar',
+ secondText: 'Segera daftarkan diri anda ke aplikasi ini untuk akses penuh ke fitur kami!',
+ ),
+ SizedBox(height: 50.h),
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 8.w),
+ child: TypographyStyles.body(
+ 'Nama',
+ color: GrayColors.gray800,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ SizedBox(height: 16.h),
+ InputForm(
+ controller: _name,
+ hintText: 'SUPARJO',
+ svgIconPath: 'assets/icons/ic_account.svg',
+ validator: Validators.validatorName,
+ inputFormatters: [
+ FilteringTextInputFormatter.allow(RegExp(r'[a-zA-Z\s]')),
+ UpperCaseTextFormatter(),
+ ],
+ textInputType: TextInputType.text,
+ ),
+ SizedBox(height: 20.h),
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 8.w),
+ child: TypographyStyles.body(
+ 'Email',
+ color: GrayColors.gray800,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ SizedBox(height: 16.h),
+ InputForm(
+ controller: _email,
+ hintText: 'example@gmail.com',
+ svgIconPath: 'assets/icons/ic_email.svg',
+ validator: Validators.validatorEmail,
+ textInputType: TextInputType.emailAddress,
+ ),
+ SizedBox(height: 20.h),
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 8.w),
+ child: TypographyStyles.body(
+ 'Password',
+ color: GrayColors.gray800,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ SizedBox(height: 16.h),
+ InputPassword(
+ controller: _password,
+ hintText: '••••••••••',
+ svgIconPath: 'assets/icons/ic_padlock.svg',
+ validator: Validators.validatorPassword,
+ ),
+ SizedBox(height: 20.h),
+ Padding(
+ padding: EdgeInsets.symmetric(horizontal: 8.w),
+ child: TypographyStyles.body(
+ 'Konfirmasi Password',
+ color: GrayColors.gray800,
+ fontWeight: FontWeight.w500,
+ ),
+ ),
+ SizedBox(height: 16.h),
+ InputPassword(
+ controller: _verifPassword,
+ hintText: '••••••••••',
+ svgIconPath: 'assets/icons/ic_padlock.svg',
+ validator: Validators.validatorConfirmPassword(_password),
+ ),
+ ],
+ )),
),
),
),
@@ -102,13 +153,13 @@ class _RegisterScreenState extends State {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
- ZoomTapAnimation(
- child: ButtonFill(
- text: 'Daftar',
- textColor: Colors.white,
- onTap: () {
- Get.toNamed(Routes.STATESUCCES);
- },
+ Obx(
+ () => ZoomTapAnimation(
+ child: ButtonFill(
+ text: _authController.isLoading.value ? 'Loading...' : 'Daftar',
+ textColor: Colors.white,
+ onTap: _authController.isLoading.value ? null : _handleRegister,
+ ),
),
),
SizedBox(height: 20.h),
diff --git a/lib/presentation/screens/auth/pages/verifikasi_screen.dart b/lib/presentation/screens/auth/pages/verifikasi_screen.dart
new file mode 100644
index 0000000..1465aa2
--- /dev/null
+++ b/lib/presentation/screens/auth/pages/verifikasi_screen.dart
@@ -0,0 +1,88 @@
+import 'dart:async';
+
+import 'package:e_porter/_core/constants/colors.dart';
+import 'package:e_porter/_core/constants/typography.dart';
+import 'package:e_porter/presentation/controllers/auth_controller.dart';
+import 'package:e_porter/presentation/screens/auth/component/header_text.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_screenutil/flutter_screenutil.dart';
+import 'package:flutter_svg/flutter_svg.dart';
+import 'package:get/get.dart';
+import 'package:zoom_tap_animation/zoom_tap_animation.dart';
+
+class VerifikasiScreen extends StatefulWidget {
+ VerifikasiScreen({super.key});
+
+ @override
+ State createState() => _VerifikasiScreenState();
+}
+
+class _VerifikasiScreenState extends State {
+ final AuthController _authController = Get.find();
+
+ Timer? _timer;
+
+ @override
+ void initState() {
+ super.initState();
+ _timer = Timer.periodic(const Duration(seconds: 3), (_) async {
+ await _authController.completeEmailVerification();
+ });
+ }
+
+ @override
+ void dispose() {
+ _timer?.cancel();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Colors.white,
+ body: SafeArea(
+ child: Padding(
+ padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h),
+ child: SingleChildScrollView(
+ child: Column(
+ children: [
+ HeaderText(
+ firstText: 'Verifikasi Email',
+ secondText: 'Kami telah mengirimkan link verifikasi melalui email anda. Silahkan cek email anda',
+ ),
+ Padding(
+ padding: EdgeInsets.only(top: 20.h),
+ child: SvgPicture.asset('assets/images/il_email.svg'),
+ ),
+ SizedBox(height: 32.h),
+ _buildSendVerification(
+ onTap: _authController.resendEmailVerification,
+ )
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildSendVerification({required VoidCallback onTap}) {
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ TypographyStyles.body(
+ 'Kirim ulang verifikasi?',
+ color: GrayColors.gray600,
+ fontWeight: FontWeight.w400,
+ ),
+ SizedBox(width: 8.w),
+ ZoomTapAnimation(
+ child: GestureDetector(
+ onTap: onTap,
+ child: TypographyStyles.body('Kirim Ulang', color: Colors.blue.shade600),
+ ),
+ )
+ ],
+ );
+ }
+}
diff --git a/lib/presentation/screens/routes/app_rountes.dart b/lib/presentation/screens/routes/app_rountes.dart
index f82a027..b665dd4 100644
--- a/lib/presentation/screens/routes/app_rountes.dart
+++ b/lib/presentation/screens/routes/app_rountes.dart
@@ -12,6 +12,7 @@ import 'package:e_porter/presentation/screens/auth/pages/forget_password_screen.
import 'package:e_porter/presentation/screens/auth/pages/login_screen.dart';
import 'package:e_porter/presentation/screens/auth/pages/register_screen.dart';
import 'package:e_porter/presentation/screens/auth/pages/state_succes_screen.dart';
+import 'package:e_porter/presentation/screens/auth/pages/verifikasi_screen.dart';
import 'package:e_porter/presentation/screens/boarding_pass/pages/boarding_pass_screen.dart';
import 'package:e_porter/presentation/screens/boarding_pass/pages/detail_history_porter_screen.dart';
import 'package:e_porter/presentation/screens/boarding_pass/pages/detail_ticket_screen.dart';
@@ -66,6 +67,10 @@ class AppRoutes {
page: () => LoginScreen(),
binding: AuthBinding(),
),
+ GetPage(
+ name: Routes.VERIFICATION,
+ page: () => VerifikasiScreen(),
+ ),
GetPage(
name: Routes.HOME,
page: () => MainNavigation(),
@@ -196,6 +201,7 @@ class Routes {
static const SPLASH = '/splash';
static const ONBOARDING = '/onboarding';
static const LOGIN = '/login';
+ static const VERIFICATION = '/verification';
static const HOME = '/home';
static const BOARDINGPASS = '/boarding_pass';
static const PROFILE = '/profile';