TIFNGK_E41221954/lib/screens/auth/login.dart

392 lines
13 KiB
Dart

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sijentik/component/app_theme.dart';
import 'package:sijentik/screens/auth/registerkader.dart';
import 'package:sijentik/screens/auth/lupasandiemail.dart';
import 'package:sijentik/screens/kader/homekader.dart';
import 'package:sijentik/api/api.dart';
import 'package:sijentik/screens/petugas/dashboardpetugas.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
final TextEditingController emailController = TextEditingController();
final TextEditingController passwordController = TextEditingController();
final FocusNode emailFocusNode = FocusNode();
final FocusNode passwordFocusNode = FocusNode();
bool obscurePassword = true;
bool isLoading = false;
@override
void dispose() {
emailController.dispose();
passwordController.dispose();
emailFocusNode.dispose();
passwordFocusNode.dispose();
super.dispose();
}
// =============================
// SIMPAN DATA USER
// =============================
Future<void> saveUserData(Map<String, dynamic> user) async {
final prefs = await SharedPreferences.getInstance();
await prefs.setInt('id', int.tryParse(user['id'].toString()) ?? 0);
await prefs.setString('name', user['name'] ?? '');
await prefs.setString('email', user['email'] ?? '');
await prefs.setString('address', user['address'] ?? '');
await prefs.setString('rtrw', user['rtrw'] ?? '');
await prefs.setString('role', user['role'] ?? '');
await prefs.setString('status', user['status'] ?? '');
}
// =============================
// LOGIN
// =============================
Future<void> _login() async {
if (emailController.text.isEmpty || passwordController.text.isEmpty) {
_showErrorDialog('Email dan kata sandi harus diisi');
return;
}
if (!emailController.text.contains('@')) {
_showErrorDialog('Format email tidak valid');
return;
}
setState(() {
isLoading = true;
});
try {
final response = await http.post(
Uri.parse('$baseUrl/login'),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: jsonEncode({
'email': emailController.text.trim(),
'password': passwordController.text.trim(),
}),
);
print("STATUS LOGIN: ${response.statusCode}");
print("BODY LOGIN: ${response.body}");
final data = jsonDecode(response.body);
if (!mounted) return;
setState(() {
isLoading = false;
});
if (response.statusCode == 200) {
final user = Map<String, dynamic>.from(data['user'] ?? {});
// 🔥 VALIDASI WAJIB
if (user['id'] == null) {
_showErrorDialog('ID user tidak ditemukan dari server');
return;
}
print("LOGIN SUCCESS");
print("USER FINAL: $user");
// SIMPAN
await saveUserData(user);
final String role = (user['role'] ?? '').toString().toLowerCase();
if (role == 'petugas') {
_navigateToDashboardPetugas();
} else if (role == 'kader') {
_navigateToHomeKader({
'id': user['id'],
'name': user['name'],
'email': user['email'],
'address': user['address'],
'role': user['role'],
});
} else {
_showErrorDialog('Role pengguna tidak dikenali');
}
} else {
_showErrorDialog(data['error'] ?? data['message'] ?? 'Login gagal');
}
} catch (e) {
if (!mounted) return;
setState(() {
isLoading = false;
});
_showErrorDialog('Tidak dapat terhubung ke server: $e');
}
}
// =============================
// ERROR DIALOG
// =============================
void _showErrorDialog(String message) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('Login Gagal'),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('OK'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFFF5F9FF),
body: Stack(
children: [
Positioned(
top: 0,
left: 0,
right: 0,
child: Container(
height: 280,
decoration: BoxDecoration(
color: AppColors.button,
borderRadius: const BorderRadius.only(
bottomLeft: Radius.circular(195),
bottomRight: Radius.circular(195),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 15,
spreadRadius: 1,
offset: const Offset(0, 13),
),
],
),
),
),
SafeArea(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const SizedBox(height: 40),
Image.asset('assets/images/logodua.png', width: 150),
const SizedBox(height: 55),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.3),
blurRadius: 12,
spreadRadius: 1,
offset: const Offset(0, 6),
),
],
),
child: Column(
children: [
const Text(
'MASUK',
style: TextStyle(
fontSize: 26,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
const SizedBox(height: 30),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Email',
style: TextStyle(
fontSize: 16,
color: Colors.grey[700],
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(height: 8),
TextField(
controller: emailController,
focusNode: emailFocusNode,
decoration: InputDecoration(
hintText: 'email@gmail.com',
prefixIcon: const Icon(Icons.email_outlined),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
onSubmitted: (_) => _login(),
),
const SizedBox(height: 20),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Kata Sandi',
style: TextStyle(
fontSize: 16,
color: Colors.grey[700],
fontWeight: FontWeight.w500,
),
),
),
const SizedBox(height: 8),
TextField(
controller: passwordController,
focusNode: passwordFocusNode,
obscureText: obscurePassword,
decoration: InputDecoration(
hintText: '********',
prefixIcon: const Icon(Icons.lock_outline),
suffixIcon: IconButton(
icon: Icon(
obscurePassword
? Icons.visibility_off
: Icons.visibility,
),
onPressed: () {
setState(() {
obscurePassword = !obscurePassword;
});
},
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
),
onSubmitted: (_) => _login(),
),
const SizedBox(height: 10),
Row(
children: [
TextButton(
onPressed: _navigateLupaKataSandi,
child: const Text(
'Lupa Kata Sandi?',
style: TextStyle(
color: Color(0xFF1E88E5),
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 10),
SizedBox(
width: double.infinity,
height: 48,
child: ElevatedButton(
onPressed: isLoading ? null : _login,
style: ElevatedButton.styleFrom(
backgroundColor: AppColors.button,
),
child: isLoading
? const CircularProgressIndicator(
color: Colors.white,
)
: const Text(
'Masuk',
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
const SizedBox(height: 15),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Belum punya akun? ',
style: TextStyle(color: Colors.black87),
),
GestureDetector(
onTap: _navigateToRegister,
child: const Text(
'Daftar',
style: TextStyle(
color: Color(0xFF1E88E5),
fontWeight: FontWeight.bold,
),
),
),
],
),
],
),
),
),
],
),
),
),
],
),
);
}
void _navigateToHomeKader(Map<String, dynamic> user) {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => HomeKaderPage(user: user)),
(route) => false,
);
}
void _navigateToDashboardPetugas() {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (context) => const DashboardPetugas()),
(route) => false,
);
}
void _navigateToRegister() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const RegisterKaderPage()),
);
}
void _navigateLupaKataSandi() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const LupaKataSandiEmailPage()),
);
}
}