Update: 2026-06-12 20:40:40
This commit is contained in:
@@ -8,12 +8,13 @@
|
||||
|
||||
<domain-config cleartextTrafficPermitted="false">
|
||||
<domain includeSubdomains="true">intaleq.xyz</domain>
|
||||
<domain includeSubdomains="true">siromove.com</domain>
|
||||
|
||||
<pin-set expiration="2027-01-01">
|
||||
<!-- <pin digest="SHA-256">pXmP2hTQLxDEvlTVmP5N7xpiA32sycBsxB6hBFT2uL4=</pin> -->
|
||||
<pin digest="SHA-256">XJXX7XthMj5VlSHfvo1q73sY7orJ9Wle0X4avj0/Vwo=</pin>
|
||||
|
||||
<pin-set expiration="2028-01-01">
|
||||
<!-- Primary: ISRG Root X1 (RSA) -->
|
||||
<pin digest="SHA-256">C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHESsl=</pin>
|
||||
<!-- Backup: ISRG Root X2 (ECDSA) -->
|
||||
<pin digest="SHA-256">diGVwiVYbubAI3RW4hB9xU8e/CH2GnkuvVFZE8zmgzI=</pin>
|
||||
</pin-set>
|
||||
</domain-config>
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@ import '../functions/encrypt_decrypt.dart';
|
||||
|
||||
class OtpHelper extends GetxController {
|
||||
static final String _sendOtpUrl =
|
||||
'${AppLink.server}/Admin/auth/send_otp_admin.php';
|
||||
'${AppLink.server}/auth/otp/request.php';
|
||||
static final String _verifyOtpUrl =
|
||||
'${AppLink.server}/Admin/auth/verify_otp_admin.php';
|
||||
'${AppLink.server}/auth/otp/verify.php';
|
||||
static final String _checkAdminLogin =
|
||||
'${AppLink.server}/Admin/auth/login.php';
|
||||
|
||||
@@ -29,7 +29,7 @@ class OtpHelper extends GetxController {
|
||||
// await CRUD().getJWT();
|
||||
final response = await CRUD().post(
|
||||
link: _sendOtpUrl,
|
||||
payload: {'receiver': phoneNumber},
|
||||
payload: {'receiver': phoneNumber, 'user_type': 'admin'},
|
||||
);
|
||||
// Log.print('_sendOtpUrl: ${_sendOtpUrl}');
|
||||
// Log.print('response: ${response}');
|
||||
@@ -54,8 +54,9 @@ class OtpHelper extends GetxController {
|
||||
link: _verifyOtpUrl,
|
||||
payload: {
|
||||
'phone_number': phoneNumber,
|
||||
'otp': otp,
|
||||
'device_number': box.read(BoxName.fingerPrint)
|
||||
'token_code': otp,
|
||||
'user_type': 'admin',
|
||||
'device_number': box.read(BoxName.fingerPrint) ?? ''
|
||||
},
|
||||
);
|
||||
|
||||
@@ -79,7 +80,7 @@ class OtpHelper extends GetxController {
|
||||
}
|
||||
|
||||
/// تسجيل الدخول بكلمة المرور والبصمة
|
||||
Future<bool> loginWithPassword(String password) async {
|
||||
Future<bool> loginWithPassword(String password, [String phone = '']) async {
|
||||
try {
|
||||
final fingerprint = box.read(BoxName.fingerPrint);
|
||||
final response = await CRUD().post(
|
||||
@@ -87,6 +88,7 @@ class OtpHelper extends GetxController {
|
||||
payload: {
|
||||
'fingerprint': fingerprint,
|
||||
'password': password,
|
||||
'phone': phone,
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
61
siro_admin/lib/controller/auth/register_controller.dart
Normal file
61
siro_admin/lib/controller/auth/register_controller.dart
Normal file
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:siro_admin/constant/links.dart';
|
||||
import 'package:siro_admin/controller/functions/crud.dart';
|
||||
import 'package:siro_admin/main.dart'; // للوصول لـ box
|
||||
|
||||
class AdminRegisterController extends GetxController {
|
||||
final nameCtrl = TextEditingController();
|
||||
final phoneCtrl = TextEditingController();
|
||||
final passCtrl = TextEditingController();
|
||||
final formKey = GlobalKey<FormState>();
|
||||
|
||||
var isLoading = false.obs;
|
||||
|
||||
Future<void> register() async {
|
||||
if (!formKey.currentState!.validate()) return;
|
||||
|
||||
isLoading.value = true;
|
||||
try {
|
||||
// جلب البصمة، وإذا لم تكن موجودة يمكن محاولة توليدها أو طلبها
|
||||
String fingerprint = box.read('fingerprint') ?? '';
|
||||
|
||||
final response = await CRUD().post(
|
||||
link: '${AppLink.server}/Admin/auth/register.php',
|
||||
payload: {
|
||||
'name': nameCtrl.text.trim(),
|
||||
'phone': phoneCtrl.text.trim(),
|
||||
'password': passCtrl.text.trim(),
|
||||
'fingerprint': fingerprint,
|
||||
},
|
||||
);
|
||||
|
||||
if (response != 'failure') {
|
||||
if (response['status'] == 'pending') {
|
||||
Get.snackbar('نجاح', response['message'] ?? 'تم تقديم الطلب بنجاح',
|
||||
backgroundColor: Colors.green.withOpacity(0.8),
|
||||
colorText: Colors.white);
|
||||
Future.delayed(const Duration(seconds: 2), () => Get.back());
|
||||
} else {
|
||||
Get.snackbar('خطأ', 'حدث خطأ غير متوقع',
|
||||
backgroundColor: Colors.red.withOpacity(0.8),
|
||||
colorText: Colors.white);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
Get.snackbar('خطأ', 'فشل في الاتصال بالخادم',
|
||||
backgroundColor: Colors.red.withOpacity(0.8),
|
||||
colorText: Colors.white);
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
nameCtrl.dispose();
|
||||
phoneCtrl.dispose();
|
||||
passCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import 'views/admin/drivers/driver_documents_review_page.dart';
|
||||
List<GetPage<dynamic>> routes = [
|
||||
GetPage(name: "/", page: () => const AdminHomePage()),
|
||||
GetPage(name: "/login", page: () => const AdminLoginPage()),
|
||||
GetPage(name: "/register", page: () => const RegisterPage()),
|
||||
GetPage(name: "/register", page: () => const AdminRegisterPage()),
|
||||
GetPage(name: "/promo", page: () => PromoManagementPage()),
|
||||
GetPage(name: "/kazan", page: () => KazanEditorPage()),
|
||||
GetPage(name: "/complaints", page: () => ComplaintListPage()),
|
||||
|
||||
@@ -41,6 +41,7 @@ class _AdminLoginPageState extends State<AdminLoginPage>
|
||||
|
||||
Future<void> _submit() async {
|
||||
final password = _passwordController.text.trim();
|
||||
final phone = _phoneController.text.trim();
|
||||
|
||||
if (password.isEmpty) {
|
||||
Get.snackbar('خطأ', 'يرجى إدخال كلمة المرور');
|
||||
@@ -50,7 +51,7 @@ class _AdminLoginPageState extends State<AdminLoginPage>
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
final otpHelper = Get.find<OtpHelper>();
|
||||
bool success = await otpHelper.loginWithPassword(password);
|
||||
bool success = await otpHelper.loginWithPassword(password, phone);
|
||||
|
||||
if (success) {
|
||||
Get.offAll(() => const AdminHomePage());
|
||||
@@ -194,6 +195,55 @@ class _AdminLoginPageState extends State<AdminLoginPage>
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
// ── Field label (Phone) ─────────────────────────────
|
||||
const Row(
|
||||
children: [
|
||||
Icon(Icons.phone_android_rounded,
|
||||
color: _C.accent, size: 16),
|
||||
SizedBox(width: 8),
|
||||
Text(
|
||||
'رقم الهاتف (لأول دخول فقط)',
|
||||
style: TextStyle(
|
||||
color: _C.textSec,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// ── Phone field ─────────────────────────────
|
||||
TextFormField(
|
||||
controller: _phoneController,
|
||||
keyboardType: TextInputType.phone,
|
||||
style: const TextStyle(
|
||||
color: _C.textPrimary,
|
||||
fontSize: 16,
|
||||
),
|
||||
decoration: InputDecoration(
|
||||
hintText: '07XXXXXXXX',
|
||||
hintStyle: const TextStyle(color: _C.textSec),
|
||||
filled: true,
|
||||
fillColor: _C.inputBg,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: const BorderSide(
|
||||
color: _C.border, width: 1),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
borderSide: const BorderSide(
|
||||
color: _C.accent, width: 1.5),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 16),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
// ── Field label (Password) ─────────────────────────────
|
||||
const Row(
|
||||
@@ -319,7 +369,32 @@ class _AdminLoginPageState extends State<AdminLoginPage>
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
const SizedBox(height: 30),
|
||||
|
||||
// ── Register Link ───────────────────────────────────────────
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text(
|
||||
'ليس لديك حساب مشرف؟',
|
||||
style: TextStyle(color: _C.textSec, fontSize: 14),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Get.toNamed('/register'); // أو الانتقال المباشر للصفحة
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor: _C.accent,
|
||||
textStyle: const TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
child: const Text('تسجيل حساب جديد'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,181 +1,158 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import '../../constant/box_name.dart';
|
||||
import '../../constant/links.dart';
|
||||
import '../../main.dart';
|
||||
import '../../views/widgets/snackbar.dart';
|
||||
import '../../controller/functions/crud.dart';
|
||||
import 'package:siro_admin/controller/auth/register_controller.dart';
|
||||
|
||||
class _C {
|
||||
static const bg = Color(0xFF0A0D14);
|
||||
static const card = Color(0xFF161D2E);
|
||||
static const border = Color(0xFF1F2D4A);
|
||||
static const accent = Color(0xFF00E5FF);
|
||||
static const textPrimary = Color(0xFFE8F0FE);
|
||||
static const textSec = Color(0xFF7A8BAA);
|
||||
static const inputBg = Color(0xFF0C1120);
|
||||
}
|
||||
|
||||
class RegisterPage extends StatefulWidget {
|
||||
const RegisterPage({super.key});
|
||||
|
||||
@override
|
||||
State<RegisterPage> createState() => _RegisterPageState();
|
||||
}
|
||||
|
||||
class _RegisterPageState extends State<RegisterPage> {
|
||||
final _nameController = TextEditingController();
|
||||
final _phoneController = TextEditingController();
|
||||
final _passwordController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
bool _isLoading = false;
|
||||
|
||||
Future<void> _register() async {
|
||||
if (!_formKey.currentState!.validate()) return;
|
||||
|
||||
setState(() => _isLoading = true);
|
||||
|
||||
try {
|
||||
final fingerprint = box.read(BoxName.fingerPrint);
|
||||
final response = await CRUD().post(
|
||||
link: '${AppLink.server}/Admin/auth/register.php',
|
||||
payload: {
|
||||
'name': _nameController.text.trim(),
|
||||
'phone': _phoneController.text.trim(),
|
||||
'password': _passwordController.text.trim(),
|
||||
'fingerprint': fingerprint,
|
||||
},
|
||||
);
|
||||
|
||||
if (response != 'failure') {
|
||||
mySnackbarSuccess(response['message'] ?? 'تم تقديم طلبك بنجاح');
|
||||
Get.back(); // العودة لصفحة الدخول
|
||||
}
|
||||
} catch (e) {
|
||||
mySnackeBarError('حدث خطأ أثناء التسجيل: $e');
|
||||
} finally {
|
||||
setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_nameController.dispose();
|
||||
_phoneController.dispose();
|
||||
_passwordController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
class AdminRegisterPage extends StatelessWidget {
|
||||
const AdminRegisterPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.put(AdminRegisterController());
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: _C.bg,
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
leading: IconButton(
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded, color: _C.accent),
|
||||
icon: const Icon(Icons.arrow_back_ios, color: _C.textPrimary),
|
||||
onPressed: () => Get.back(),
|
||||
),
|
||||
),
|
||||
body: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
child: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: [
|
||||
const Icon(Icons.person_add_rounded, color: _C.accent, size: 64),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'طلب انضمام جديد',
|
||||
style: TextStyle(
|
||||
color: _C.textPrimary,
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'سيتم مراجعة طلبك من قبل الإدارة',
|
||||
style: TextStyle(color: _C.textSec, fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
_buildCard(),
|
||||
const SizedBox(height: 32),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCard() {
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: _C.card,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(color: _C.accent.withAlpha(25)), // 0.1 * 255 ≈ 25
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildField('الاسم الكامل', _nameController, Icons.person_outline),
|
||||
const SizedBox(height: 20),
|
||||
_buildField('رقم الهاتف', _phoneController, Icons.phone_android, isPhone: true),
|
||||
const SizedBox(height: 20),
|
||||
_buildField('كلمة المرور', _passwordController, Icons.lock_outline, isPass: true),
|
||||
const SizedBox(height: 32),
|
||||
_isLoading
|
||||
? const Center(child: CircularProgressIndicator(color: _C.accent))
|
||||
: ElevatedButton(
|
||||
onPressed: _register,
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: _C.accent,
|
||||
foregroundColor: Colors.black,
|
||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(14),
|
||||
body: SafeArea(
|
||||
child: Center(
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 28),
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 440),
|
||||
child: Form(
|
||||
key: controller.formKey,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.admin_panel_settings, size: 80, color: _C.accent),
|
||||
const SizedBox(height: 24),
|
||||
const Text(
|
||||
'تسجيل مشرف جديد',
|
||||
style: TextStyle(
|
||||
color: _C.textPrimary,
|
||||
fontSize: 26,
|
||||
fontWeight: FontWeight.w800,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Text('إرسال الطلب', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'أدخل بياناتك لتقديم طلب الإشراف',
|
||||
style: TextStyle(color: _C.textSec, fontSize: 14),
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(28),
|
||||
decoration: BoxDecoration(
|
||||
color: _C.card,
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
border: Border.all(color: _C.accent.withOpacity(0.18), width: 1.2),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_buildLabel('الاسم الكامل', Icons.person),
|
||||
const SizedBox(height: 10),
|
||||
_buildTextField(
|
||||
controller: controller.nameCtrl,
|
||||
hint: 'أحمد محمود',
|
||||
icon: Icons.person_outline,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildLabel('رقم الهاتف', Icons.phone_android),
|
||||
const SizedBox(height: 10),
|
||||
_buildTextField(
|
||||
controller: controller.phoneCtrl,
|
||||
hint: '07XXXXXXXX',
|
||||
keyboardType: TextInputType.phone,
|
||||
icon: Icons.phone_outlined,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
_buildLabel('كلمة المرور', Icons.lock_outline),
|
||||
const SizedBox(height: 10),
|
||||
_buildTextField(
|
||||
controller: controller.passCtrl,
|
||||
hint: '••••••••',
|
||||
obscureText: true,
|
||||
icon: Icons.lock_outline,
|
||||
),
|
||||
const SizedBox(height: 40),
|
||||
Obx(() => MaterialButton(
|
||||
onPressed: controller.isLoading.value ? null : () => controller.register(),
|
||||
color: _C.accent,
|
||||
height: 55,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: controller.isLoading.value
|
||||
? const CircularProgressIndicator(color: _C.bg)
|
||||
: const Text(
|
||||
'تقديم الطلب',
|
||||
style: TextStyle(color: _C.bg, fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
)),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildField(String label, TextEditingController ctrl, IconData icon,
|
||||
{bool isPhone = false, bool isPass = false}) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
Widget _buildLabel(String text, IconData icon) {
|
||||
return Row(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(icon, color: _C.accent, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(label, style: const TextStyle(color: _C.textSec, fontSize: 13)),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
TextFormField(
|
||||
controller: ctrl,
|
||||
obscureText: isPass,
|
||||
keyboardType: isPhone ? TextInputType.phone : TextInputType.text,
|
||||
style: const TextStyle(color: _C.textPrimary),
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: _C.inputBg,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
||||
),
|
||||
validator: (val) => val == null || val.isEmpty ? 'هذا الحقل مطلوب' : null,
|
||||
Icon(icon, color: _C.accent, size: 16),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
text,
|
||||
style: const TextStyle(color: _C.textSec, fontSize: 13, fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTextField({
|
||||
required TextEditingController controller,
|
||||
required String hint,
|
||||
required IconData icon,
|
||||
bool obscureText = false,
|
||||
TextInputType keyboardType = TextInputType.text,
|
||||
}) {
|
||||
return TextFormField(
|
||||
controller: controller,
|
||||
obscureText: obscureText,
|
||||
keyboardType: keyboardType,
|
||||
style: const TextStyle(color: _C.textPrimary, fontSize: 16),
|
||||
decoration: InputDecoration(
|
||||
hintText: hint,
|
||||
hintStyle: const TextStyle(color: _C.textSec),
|
||||
filled: true,
|
||||
fillColor: _C.inputBg,
|
||||
prefixIcon: Icon(icon, color: _C.textSec),
|
||||
border: OutlineInputBorder(borderRadius: BorderRadius.circular(14), borderSide: BorderSide.none),
|
||||
focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(14), borderSide: const BorderSide(color: _C.accent, width: 1.5)),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
|
||||
),
|
||||
validator: (val) => (val == null || val.isEmpty) ? 'هذا الحقل مطلوب' : null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user