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

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) {
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);

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: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<LoginController> {
const LoginView({super.key});
@ -11,64 +16,47 @@ class LoginView extends GetView<LoginController> {
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()
],
),
),