From 32404aceae863488a244f8c7073e3f87e2a12a0e Mon Sep 17 00:00:00 2001 From: akhdanre Date: Tue, 22 Apr 2025 13:38:37 +0700 Subject: [PATCH] feat: login page done --- lib/component/global_button.dart | 27 ++++++ lib/component/global_text_field.dart | 40 +++++++- lib/component/label_text_field.dart | 27 ++++++ .../login/controllers/login_controller.dart | 14 +-- .../presentation/component/google_button.dart | 29 ++++++ .../component/register_text_button.dart | 33 +++++++ .../login/presentation/login_page.dart | 92 ++++++++----------- 7 files changed, 198 insertions(+), 64 deletions(-) create mode 100644 lib/component/global_button.dart create mode 100644 lib/component/label_text_field.dart create mode 100644 lib/feature/login/presentation/component/google_button.dart create mode 100644 lib/feature/login/presentation/component/register_text_button.dart diff --git a/lib/component/global_button.dart b/lib/component/global_button.dart new file mode 100644 index 0000000..3bd07c5 --- /dev/null +++ b/lib/component/global_button.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class GlobalButton extends StatelessWidget { + final VoidCallback onPressed; + final String text; + + const GlobalButton({super.key, required this.onPressed, required this.text}); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + foregroundColor: Colors.black, + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + padding: const EdgeInsets.symmetric(vertical: 16), + ), + onPressed: onPressed, + child: Text(text), + ), + ); + } +} diff --git a/lib/component/global_text_field.dart b/lib/component/global_text_field.dart index 5529475..80fff87 100644 --- a/lib/component/global_text_field.dart +++ b/lib/component/global_text_field.dart @@ -3,15 +3,49 @@ import 'package:flutter/material.dart'; class GlobalTextField extends StatelessWidget { final TextEditingController controller; final String? hintText; + final String? labelText; + final bool isPassword; + final bool obscureText; + final VoidCallback? onToggleVisibility; - const GlobalTextField({super.key, required this.controller, this.hintText}); + const GlobalTextField({ + super.key, + required this.controller, + this.hintText, + this.labelText, + this.isPassword = false, + this.obscureText = false, + this.onToggleVisibility, + }); @override Widget build(BuildContext context) { return TextField( + controller: controller, + obscureText: isPassword ? obscureText : false, decoration: InputDecoration( - border: OutlineInputBorder(), - labelText: hintText, + labelText: labelText, + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide.none, + ), + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide.none, + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(16), + borderSide: BorderSide.none, + ), + hintText: hintText, + filled: true, + fillColor: const Color.fromARGB(255, 238, 238, 238), + suffixIcon: isPassword + ? IconButton( + icon: Icon(obscureText ? Icons.visibility_off : Icons.visibility), + onPressed: onToggleVisibility, + ) + : null, ), ); } diff --git a/lib/component/label_text_field.dart b/lib/component/label_text_field.dart new file mode 100644 index 0000000..23cd9cc --- /dev/null +++ b/lib/component/label_text_field.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class LabelTextField extends StatelessWidget { + final String label; + final double fontSize; + final FontWeight fontWeight; + final Alignment alignment; + const LabelTextField( + {super.key, required, required this.label, this.fontSize = 16, this.alignment = Alignment.centerLeft, this.fontWeight = FontWeight.bold}); + + @override + Widget build(BuildContext context) { + return Align( + alignment: alignment, + child: Padding( + padding: EdgeInsets.fromLTRB(10, 5, 0, 5), + child: Text( + label, + style: TextStyle( + fontSize: fontSize, + fontWeight: fontWeight, + ), + ), + ), + ); + } +} diff --git a/lib/feature/login/controllers/login_controller.dart b/lib/feature/login/controllers/login_controller.dart index 225a490..fb8cc20 100644 --- a/lib/feature/login/controllers/login_controller.dart +++ b/lib/feature/login/controllers/login_controller.dart @@ -41,14 +41,9 @@ class LoginController extends GetxController { if (response.statusCode == 200) { var data = jsonDecode(response.body); - String token = data['token']; - - // await _secureStorage.write(key: "auth_token", value: token); - - Get.snackbar("Success", "Login successful!"); - logC.i("Login Token: $token"); } else { var errorMsg = jsonDecode(response.body)['message'] ?? "Invalid credentials"; + logC.i(errorMsg); Get.snackbar("Error", errorMsg); } } catch (e, stackTrace) { @@ -84,19 +79,20 @@ class LoginController extends GetxController { // Send ID Token to backend var response = await http.post( Uri.parse("${APIEndpoint.baseUrl}${APIEndpoint.loginGoogle}"), - body: jsonEncode({"id_token": idToken}), // Ensure correct key + body: jsonEncode({"token_id": idToken}), // Ensure correct key headers: {"Content-Type": "application/json"}, ); if (response.statusCode == 200) { var data = jsonDecode(response.body); - String backendToken = data['token']; // Token received from your backend + Get.snackbar("Success", "Google login successful!"); - logC.i("Backend Auth Token: $backendToken"); + // logC.i("Backend Auth Token: $backendToken"); } else { var errorMsg = jsonDecode(response.body)['message'] ?? "Google login failed"; Get.snackbar("Error", errorMsg); + logC.i(errorMsg); } } catch (e, stackTrace) { logC.e("Google Sign-In Error: $e", stackTrace: stackTrace); diff --git a/lib/feature/login/presentation/component/google_button.dart b/lib/feature/login/presentation/component/google_button.dart new file mode 100644 index 0000000..032c5e1 --- /dev/null +++ b/lib/feature/login/presentation/component/google_button.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class GoogleButton extends StatelessWidget { + final VoidCallback onPress; + const GoogleButton({super.key, required this.onPress}); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: double.infinity, + child: ElevatedButton.icon( + onPressed: onPress, + icon: Image.asset( + 'assets/logo/google_logo.png', + height: 24, + ), + label: const Text("Masuk dengan Google"), + style: ElevatedButton.styleFrom( + foregroundColor: Colors.black, + backgroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + ), + padding: const EdgeInsets.symmetric(vertical: 16), + ), + ), + ); + } +} diff --git a/lib/feature/login/presentation/component/register_text_button.dart b/lib/feature/login/presentation/component/register_text_button.dart new file mode 100644 index 0000000..4c33be2 --- /dev/null +++ b/lib/feature/login/presentation/component/register_text_button.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +class RegisterTextButton extends StatelessWidget { + final VoidCallback? onTap; + const RegisterTextButton({super.key, this.onTap}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Belum punya akun? ", + style: TextStyle( + fontSize: 14, + color: Colors.black, + ), + ), + GestureDetector( + onTap: onTap, + child: const Text( + "Daftar", + style: TextStyle( + fontSize: 14, + color: Color.fromARGB(255, 0, 122, 255), + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ); + } +} diff --git a/lib/feature/login/presentation/login_page.dart b/lib/feature/login/presentation/login_page.dart index 3dc263c..7cc58c0 100644 --- a/lib/feature/login/presentation/login_page.dart +++ b/lib/feature/login/presentation/login_page.dart @@ -1,6 +1,11 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:quiz_app/component/global_button.dart'; +import 'package:quiz_app/component/global_text_field.dart'; +import 'package:quiz_app/component/label_text_field.dart'; import 'package:quiz_app/feature/login/controllers/login_controller.dart'; +import 'package:quiz_app/feature/login/presentation/component/google_button.dart'; +import 'package:quiz_app/feature/login/presentation/component/register_text_button.dart'; class LoginView extends GetView { const LoginView({super.key}); @@ -11,64 +16,47 @@ class LoginView extends GetView { body: SafeArea( child: Padding( padding: const EdgeInsets.all(16.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, + child: ListView( children: [ - const Text("Login Page", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)), - const SizedBox(height: 20), - - // Email Input - TextField( + Padding( + padding: const EdgeInsets.symmetric(vertical: 40), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: const [ + Text("GEN", style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold)), + Text("SO", style: TextStyle(fontSize: 36, fontWeight: FontWeight.bold, color: Colors.red)), + ], + ), + ), + LabelTextField( + label: "Log In", + fontSize: 24, + ), + const SizedBox(height: 10), + LabelTextField(label: "Email"), + GlobalTextField( controller: controller.emailController, - decoration: InputDecoration( - labelText: "Email", - border: OutlineInputBorder(), - ), ), const SizedBox(height: 10), - - // Password Input dengan fitur show/hide - Obx(() => TextField( - controller: controller.passwordController, - obscureText: controller.isPasswordHidden.value, - decoration: InputDecoration( - labelText: "Password", - border: OutlineInputBorder(), - suffixIcon: IconButton( - icon: Icon(controller.isPasswordHidden.value ? Icons.visibility_off : Icons.visibility), - onPressed: controller.togglePasswordVisibility, - ), - ), - )), + LabelTextField(label: "Password"), + Obx( + () => GlobalTextField( + controller: controller.passwordController, + isPassword: true, + obscureText: controller.isPasswordHidden.value, + onToggleVisibility: controller.togglePasswordVisibility, + ), + ), + const SizedBox(height: 40), + GlobalButton(onPressed: controller.loginWithEmail, text: "Masuk"), const SizedBox(height: 20), - - // Login Button - ElevatedButton( - onPressed: () => controller.loginWithEmail(), - child: const Text("Login with Email"), - ), - const SizedBox(height: 10), - - // Google Sign-In Button - ElevatedButton.icon( - onPressed: () => controller.loginWithGoogle(), - icon: Image.asset( - 'assets/logo/google_logo.png', // Pastikan logo Google ada di folder assets - height: 24, - ), - label: const Text("Login with Google"), - style: ElevatedButton.styleFrom( - backgroundColor: Colors.white, - foregroundColor: Colors.black, - elevation: 2, - ), - ), - - ElevatedButton( - onPressed: () => controller.logout(), - child: const Text("log out"), + LabelTextField(label: "OR", alignment: Alignment.center), + const SizedBox(height: 20), + GoogleButton( + onPress: controller.loginWithGoogle, ), + const SizedBox(height: 20), + RegisterTextButton() ], ), ),