feat: login page done

This commit is contained in:
akhdanre 2025-04-22 13:38:37 +07:00
parent 2763575e1b
commit 32404aceae
7 changed files with 198 additions and 64 deletions

View File

@ -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),
),
);
}
}

View File

@ -3,15 +3,49 @@ import 'package:flutter/material.dart';
class GlobalTextField extends StatelessWidget { class GlobalTextField extends StatelessWidget {
final TextEditingController controller; final TextEditingController controller;
final String? hintText; 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return TextField( return TextField(
controller: controller,
obscureText: isPassword ? obscureText : false,
decoration: InputDecoration( decoration: InputDecoration(
border: OutlineInputBorder(), labelText: labelText,
labelText: hintText, 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,
), ),
); );
} }

View File

@ -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,
),
),
),
);
}
}

View File

@ -41,14 +41,9 @@ class LoginController extends GetxController {
if (response.statusCode == 200) { if (response.statusCode == 200) {
var data = jsonDecode(response.body); 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 { } else {
var errorMsg = jsonDecode(response.body)['message'] ?? "Invalid credentials"; var errorMsg = jsonDecode(response.body)['message'] ?? "Invalid credentials";
logC.i(errorMsg);
Get.snackbar("Error", errorMsg); Get.snackbar("Error", errorMsg);
} }
} catch (e, stackTrace) { } catch (e, stackTrace) {
@ -84,19 +79,20 @@ class LoginController extends GetxController {
// Send ID Token to backend // Send ID Token to backend
var response = await http.post( var response = await http.post(
Uri.parse("${APIEndpoint.baseUrl}${APIEndpoint.loginGoogle}"), 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"}, headers: {"Content-Type": "application/json"},
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
var data = jsonDecode(response.body); var data = jsonDecode(response.body);
String backendToken = data['token']; // Token received from your backend
Get.snackbar("Success", "Google login successful!"); Get.snackbar("Success", "Google login successful!");
logC.i("Backend Auth Token: $backendToken"); // logC.i("Backend Auth Token: $backendToken");
} else { } else {
var errorMsg = jsonDecode(response.body)['message'] ?? "Google login failed"; var errorMsg = jsonDecode(response.body)['message'] ?? "Google login failed";
Get.snackbar("Error", errorMsg); Get.snackbar("Error", errorMsg);
logC.i(errorMsg);
} }
} catch (e, stackTrace) { } catch (e, stackTrace) {
logC.e("Google Sign-In Error: $e", stackTrace: stackTrace); logC.e("Google Sign-In Error: $e", stackTrace: stackTrace);

View File

@ -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),
),
),
);
}
}

View File

@ -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,
),
),
),
],
);
}
}

View File

@ -1,6 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.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/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<LoginController> { class LoginView extends GetView<LoginController> {
const LoginView({super.key}); const LoginView({super.key});
@ -11,64 +16,47 @@ class LoginView extends GetView<LoginController> {
body: SafeArea( body: SafeArea(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16.0), padding: const EdgeInsets.all(16.0),
child: Column( child: ListView(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const Text("Login Page", style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold)), Padding(
const SizedBox(height: 20), padding: const EdgeInsets.symmetric(vertical: 40),
child: Row(
// Email Input mainAxisAlignment: MainAxisAlignment.center,
TextField( 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, controller: controller.emailController,
decoration: InputDecoration(
labelText: "Email",
border: OutlineInputBorder(),
),
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
LabelTextField(label: "Password"),
// Password Input dengan fitur show/hide Obx(
Obx(() => TextField( () => GlobalTextField(
controller: controller.passwordController, controller: controller.passwordController,
obscureText: controller.isPasswordHidden.value, isPassword: true,
decoration: InputDecoration( obscureText: controller.isPasswordHidden.value,
labelText: "Password", onToggleVisibility: controller.togglePasswordVisibility,
border: OutlineInputBorder(), ),
suffixIcon: IconButton( ),
icon: Icon(controller.isPasswordHidden.value ? Icons.visibility_off : Icons.visibility), const SizedBox(height: 40),
onPressed: controller.togglePasswordVisibility, GlobalButton(onPressed: controller.loginWithEmail, text: "Masuk"),
),
),
)),
const SizedBox(height: 20), const SizedBox(height: 20),
LabelTextField(label: "OR", alignment: Alignment.center),
// Login Button const SizedBox(height: 20),
ElevatedButton( GoogleButton(
onPressed: () => controller.loginWithEmail(), onPress: controller.loginWithGoogle,
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"),
), ),
const SizedBox(height: 20),
RegisterTextButton()
], ],
), ),
), ),