392 lines
13 KiB
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()),
|
|
);
|
|
}
|
|
}
|