From 6516cc7c39a5afafc5bc297320aec22a3abc1370 Mon Sep 17 00:00:00 2001 From: orangdeso Date: Sun, 11 May 2025 02:07:52 +0700 Subject: [PATCH] Feat: done features sign up --- .dart_tool/package_config.json | 2 +- assets/images/il_email.svg | 143 +++++++++++++ lib/_core/validators/validators.dart | 16 ++ .../repositories/auth_repository_impl.dart | 51 ++++- lib/domain/bindings/auth_binding.dart | 8 +- lib/domain/models/user_entity.dart | 24 +-- lib/domain/repositories/auth_repository.dart | 6 +- lib/domain/usecases/auth_usecase.dart | 22 +- .../controllers/auth_controller.dart | 154 ++++++++++++-- .../screens/auth/pages/login_screen.dart | 5 +- .../screens/auth/pages/register_screen.dart | 195 +++++++++++------- .../screens/auth/pages/verifikasi_screen.dart | 88 ++++++++ .../screens/routes/app_rountes.dart | 6 + 13 files changed, 610 insertions(+), 110 deletions(-) create mode 100644 assets/images/il_email.svg create mode 100644 lib/presentation/screens/auth/pages/verifikasi_screen.dart 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';