This commit is contained in:
Hamza-Ayed
2026-05-01 01:43:59 +03:00
parent cdda136006
commit 5fc160e374
25 changed files with 1526 additions and 1856 deletions

View File

@@ -13,6 +13,7 @@ import '../../controller/admin/static_controller.dart';
import '../../controller/functions/crud.dart';
import '../../controller/notification_controller.dart';
import '../../main.dart';
import '../../print.dart';
import '../invoice/invoice_list_page.dart';
import 'captain/captain.dart';
import 'captain/syrian_driver_not_active.dart';
@@ -28,6 +29,8 @@ import 'rides/ride_lookup_page.dart';
import 'server/monitor_server_page.dart';
import 'static/static.dart';
import 'wallet/wallet.dart';
import 'staff/add_staff_page.dart';
import 'staff/pending_admins_page.dart';
class AdminHomePage extends StatefulWidget {
const AdminHomePage({super.key});
@@ -69,10 +72,16 @@ class _AdminHomePageState extends State<AdminHomePage>
duration: const Duration(seconds: 2),
)..repeat(reverse: true);
String myPhone = box.read(BoxName.adminPhone).toString();
isSuperAdmin = myPhone == '201023248456' ||
myPhone == '963992952235' ||
myPhone == '963942542053';
final String role = box.read('admin_role')?.toString() ?? 'admin';
final String myPhone = box.read(BoxName.adminPhone)?.toString() ?? '';
// التحقق من الصلاحيات: إما عن طريق الدور أو عن طريق قائمة أرقام السوبر أدمن التقليدية
isSuperAdmin = (role == 'super_admin') ||
(myPhone == '201023248456' ||
myPhone == '963992952235' ||
myPhone == '963942542053');
Log.print('AdminHomePage: role=$role, isSuperAdmin=$isSuperAdmin');
dashboardController = Get.put(DashboardController());
}
@@ -760,6 +769,8 @@ class _AdminHomePageState extends State<AdminHomePage>
const Color(0xFF80CBC4), () => Get.to(() => InvoiceListPage())),
ActionItem('الموظفون', Icons.badge_rounded, const Color(0xFFB0BEC5),
() => Get.to(() => EmployeePage())),
ActionItem('موافقة المشرفين', Icons.how_to_reg_rounded, _accent,
() => Get.to(() => const PendingAdminsPage())),
],
),
if (isSuperAdmin)
@@ -804,19 +815,37 @@ class _AdminHomePageState extends State<AdminHomePage>
))),
],
),
if (isSuperAdmin)
ActionCategory(
title: 'إدارة الكوادر',
items: [
ActionItem(
'إضافة مدير',
Icons.admin_panel_settings_rounded,
_accent,
() => Get.to(() => const AddStaffPage(role: 'admin')),
),
ActionItem(
'إضافة خدمة عملاء',
Icons.support_agent_rounded,
_info,
() => Get.to(() => const AddStaffPage(role: 'service')),
),
],
),
];
}
List<Map<String, dynamic>> _getDetailedStats(
dynamic data, DashboardController controller) {
return [
// if (isSuperAdmin)
// {
// 'title': 'رصيد الرسائل',
// 'value': controller.creditSMS,
// 'icon': Icons.sms_rounded,
// 'color': _info,
// },
if (isSuperAdmin)
{
'title': 'رصيد الرسائل',
'value': controller.creditSMS,
'icon': Icons.sms_rounded,
'color': _info,
},
{
'title': 'مكتملة',
'value': data['completed_rides'],

View File

@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sefer_admin1/controller/functions/crud.dart';
import 'package:sefer_admin1/controller/functions/wallet.dart'; // تأكد من المسار
import 'package:sefer_admin1/controller/functions/wallet.dart';
import '../../../constant/links.dart'; // تأكد من المسار
// --- Controller: المسؤول عن المنطق (البحث، الفحص، الإضافة) ---
class DriverGiftCheckerController extends GetxController {
@@ -25,8 +27,7 @@ class DriverGiftCheckerController extends GetxController {
Future<void> fetchDriverCache() async {
try {
final response = await CRUD().post(
link:
'https://api.intaleq.xyz/intaleq/Admin/driver/getDriverGiftPayment.php',
link: '${AppLink.server}/Admin/driver/getDriverGiftPayment.php',
payload: {'phone': phoneController.text.trim()},
);
// print('response: ${response}');
@@ -55,7 +56,17 @@ class DriverGiftCheckerController extends GetxController {
try {
// الخطوة 1: استخراج الـ ID بناءً على رقم الهاتف
var driver = driversCache.firstWhere(
(d) => d['phone'].toString().contains(phoneInput),
(d) {
String dbPhone =
d['phone'].toString().replaceAll(RegExp(r'[^0-9]'), '');
String inputPhone = phoneInput.replaceAll(RegExp(r'[^0-9]'), '');
// قارن آخر 9 أرقام لتجاوز مشكلة 09 مقابل 963
if (dbPhone.length >= 9 && inputPhone.length >= 9) {
return dbPhone.substring(dbPhone.length - 9) ==
inputPhone.substring(inputPhone.length - 9);
}
return dbPhone == inputPhone;
},
orElse: () => null,
);

View File

@@ -9,6 +9,7 @@ import 'package:sefer_admin1/constant/links.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../../constant/box_name.dart';
import '../../../controller/functions/crud.dart';
import '../../../main.dart';
class IntaleqTrackerScreen extends StatefulWidget {
@@ -107,12 +108,17 @@ class _IntaleqTrackerScreenState extends State<IntaleqTrackerScreen>
try {
String updateUrl =
"${_baseDir}getUpdatedLocationForAdmin.php?mode=${isLiveMode ? 'live' : 'day'}";
await http.get(Uri.parse(updateUrl));
print("📡 Calling Update URL: $updateUrl");
var responseUpdate = await CRUD().post(link: updateUrl, payload: {});
print("📡 Update Response: $responseUpdate");
String v = DateTime.now().millisecondsSinceEpoch.toString();
final responseLive =
await http.get(Uri.parse("${_baseDir}locations_live.json?v=$v"));
String liveUrl = "${_baseDir}locations_live.json?v=$v";
print("📡 Calling Live JSON URL: $liveUrl");
final responseLive = await http.get(Uri.parse(liveUrl));
print(
"📡 Live JSON Response (${responseLive.statusCode}): ${responseLive.body.length > 100 ? responseLive.body.substring(0, 100) : responseLive.body}");
if (responseLive.statusCode == 200) {
final data = json.decode(responseLive.body);
List drivers = (data is Map && data.containsKey('drivers'))
@@ -125,8 +131,11 @@ class _IntaleqTrackerScreenState extends State<IntaleqTrackerScreen>
});
}
final responseDay =
await http.get(Uri.parse("${_baseDir}locations_day.json?v=$v"));
String dayUrl = "${_baseDir}locations_day.json?v=$v";
print("📡 Calling Day JSON URL: $dayUrl");
final responseDay = await http.get(Uri.parse(dayUrl));
print(
"📡 Day JSON Response (${responseDay.statusCode}): ${responseDay.body.length > 100 ? responseDay.body.substring(0, 100) : responseDay.body}");
if (responseDay.statusCode == 200) {
final data = json.decode(responseDay.body);
List drivers = (data is Map && data.containsKey('drivers'))

View File

@@ -20,7 +20,9 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'package:sefer_admin1/controller/functions/encrypt_decrypt.dart' as X;
import '../../../constant/char_map.dart';
import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart';
import '../../../controller/functions/encrypt_decrypt.dart';
@@ -240,8 +242,7 @@ class _FingerprintMigrationToolState extends State<FingerprintMigrationTool> {
Container(
child: TextButton(
onPressed: () {
print(EncryptionHelper.instance.decryptData(
'dab40749cdecbfddf4696566448b384f0d272705b08b4ff779e085fbf3257026'));
print(EncryptionHelper.instance.decryptData('hbgbitbXrXrBr'));
},
child: Text(
"Decrypt Test",
@@ -259,6 +260,16 @@ class _FingerprintMigrationToolState extends State<FingerprintMigrationTool> {
),
),
),
Container(
child: TextButton(
onPressed: () {
print(r('hbgbitbXrXrBr'));
},
child: Text(
"decrypt X.r",
),
),
),
const SizedBox(height: 24),

View File

@@ -537,11 +537,19 @@ class PackageController extends GetxController {
fetchPackages() async {
isLoading.value = true;
var response = await CRUD().get(link: AppLink.getPackages, payload: {});
if (response != 'failure') {
var jsonData = jsonDecode(response);
packages = jsonData['message'];
if (response is String && (response == 'failure' || response == 'token_expired')) {
isLoading.value = false;
return;
}
try {
var jsonData = response is String ? jsonDecode(response) : response;
packages = jsonData['message'] ?? [];
Log.print('✅ Decoded packages: ${packages.length} items');
update();
Log.print('jsonData: $jsonData');
} catch (e) {
Log.print('❌ Error parsing packages: $e');
}
isLoading.value = false;
}

View File

@@ -0,0 +1,195 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../../../controller/admin/staff_controller.dart';
class AddStaffPage extends StatelessWidget {
final String role; // 'admin' or 'service'
const AddStaffPage({super.key, required this.role});
@override
Widget build(BuildContext context) {
final controller = Get.put(StaffController());
controller.selectedRole = role;
const Color bgColor = Color(0xFF0D1117);
const Color inputColor = Color(0xFF161B22);
const Color accentColor = Color(0xFF00D4AA);
return Scaffold(
backgroundColor: bgColor,
appBar: AppBar(
title: Text(role == 'admin' ? "إضافة مدير جديد" : "إضافة موظف خدمة عملاء"),
backgroundColor: bgColor,
elevation: 0,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Form(
key: controller.formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSectionTitle("المعلومات الأساسية"),
const SizedBox(height: 16),
_buildTextField(
controller: controller.nameController,
label: "الاسم الكامل",
icon: Icons.person_outline,
fillColor: inputColor,
),
const SizedBox(height: 16),
_buildTextField(
controller: controller.phoneController,
label: "رقم الهاتف",
icon: Icons.phone_android_outlined,
fillColor: inputColor,
keyboardType: TextInputType.phone,
),
const SizedBox(height: 16),
_buildTextField(
controller: controller.emailController,
label: "البريد الإلكتروني",
icon: Icons.email_outlined,
fillColor: inputColor,
keyboardType: TextInputType.emailAddress,
),
const SizedBox(height: 16),
_buildTextField(
controller: controller.passwordController,
label: "كلمة المرور",
icon: Icons.lock_outline,
fillColor: inputColor,
obscureText: true,
),
const SizedBox(height: 24),
_buildSectionTitle("معلومات إضافية"),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: _buildDropdown(
label: "الجنس",
value: controller.selectedGender,
items: ['Male', 'Female'],
onChanged: (val) => controller.selectedGender = val!,
fillColor: inputColor,
),
),
const SizedBox(width: 16),
Expanded(
child: _buildTextField(
controller: controller.birthdateController,
label: "تاريخ الميلاد",
icon: Icons.calendar_today_outlined,
fillColor: inputColor,
hint: "YYYY-MM-DD",
),
),
],
),
const SizedBox(height: 40),
GetBuilder<StaffController>(
builder: (controller) => SizedBox(
width: double.infinity,
height: 56,
child: ElevatedButton(
onPressed: controller.isLoading ? null : () => controller.registerStaff(),
style: ElevatedButton.styleFrom(
backgroundColor: accentColor,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
),
child: controller.isLoading
? const CircularProgressIndicator(color: Colors.white)
: Text(
"حفظ البيانات",
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: bgColor,
),
),
),
),
),
],
),
),
),
);
}
Widget _buildSectionTitle(String title) {
return Text(
title,
style: const TextStyle(
color: Color(0xFF7D8590),
fontSize: 13,
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
);
}
Widget _buildTextField({
required TextEditingController controller,
required String label,
required IconData icon,
required Color fillColor,
String? hint,
bool obscureText = false,
TextInputType keyboardType = TextInputType.text,
}) {
return Container(
decoration: BoxDecoration(
color: fillColor,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white.withOpacity(0.05)),
),
child: TextFormField(
controller: controller,
obscureText: obscureText,
keyboardType: keyboardType,
style: const TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: label,
hintText: hint,
hintStyle: const TextStyle(color: Colors.white24),
labelStyle: const TextStyle(color: Colors.white54),
prefixIcon: Icon(icon, color: Colors.white38),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
),
validator: (val) => val == null || val.isEmpty ? "هذا الحقل مطلوب" : null,
),
);
}
Widget _buildDropdown({
required String label,
required String value,
required List<String> items,
required Function(String?) onChanged,
required Color fillColor,
}) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: BoxDecoration(
color: fillColor,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white.withOpacity(0.05)),
),
child: DropdownButtonFormField<String>(
value: value,
dropdownColor: fillColor,
style: const TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: label,
labelStyle: const TextStyle(color: Colors.white54),
border: InputBorder.none,
),
items: items.map((e) => DropdownMenuItem(value: e, child: Text(e))).toList(),
onChanged: onChanged,
),
);
}
}

View File

@@ -0,0 +1,148 @@
import 'package:flutter/material.dart';
import '../../../constant/links.dart';
import '../../../controller/functions/crud.dart';
import '../../widgets/snackbar.dart';
class PendingAdminsPage extends StatefulWidget {
const PendingAdminsPage({super.key});
@override
State<PendingAdminsPage> createState() => _PendingAdminsPageState();
}
class _PendingAdminsPageState extends State<PendingAdminsPage> {
final CRUD _crud = CRUD();
bool _isLoading = true;
List _pendingAdmins = [];
@override
void initState() {
super.initState();
_fetchPendingAdmins();
}
Future<void> _fetchPendingAdmins() async {
setState(() => _isLoading = true);
try {
final response = await _crud.post(
link: '${AppLink.server}/Admin/auth/list_pending.php',
);
if (response != 'failure') {
setState(() {
_pendingAdmins = response['message'] ?? [];
});
}
} catch (e) {
mySnackeBarError('فشل في جلب البيانات: $e');
} finally {
setState(() => _isLoading = false);
}
}
Future<void> _handleAction(String adminId, String action) async {
try {
final response = await _crud.post(
link: '${AppLink.server}/Admin/auth/approve_admin.php',
payload: {
'admin_id': adminId,
'action': action,
},
);
if (response != 'failure') {
mySnackbarSuccess('تم تنفيذ الإجراء بنجاح');
_fetchPendingAdmins(); // تحديث القائمة
}
} catch (e) {
mySnackeBarError('حدث خطأ: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xFF0A0D14),
appBar: AppBar(
title: const Text('طلبات الانضمام المعلقة', style: TextStyle(fontWeight: FontWeight.bold)),
backgroundColor: const Color(0xFF161D2E),
elevation: 0,
),
body: _isLoading
? const Center(child: CircularProgressIndicator(color: Color(0xFF00E5FF)))
: _pendingAdmins.isEmpty
? _buildEmptyState()
: _buildList(),
);
}
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.person_search_rounded, size: 80, color: Colors.grey[800]),
const SizedBox(height: 16),
const Text('لا توجد طلبات معلقة حالياً', style: TextStyle(color: Colors.grey)),
],
),
);
}
Widget _buildList() {
return ListView.builder(
padding: const EdgeInsets.all(16),
itemCount: _pendingAdmins.length,
itemBuilder: (context, index) {
final admin = _pendingAdmins[index];
return Container(
margin: const EdgeInsets.only(bottom: 16),
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF161D2E),
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFF1F2D4A)),
),
child: Column(
children: [
ListTile(
contentPadding: EdgeInsets.zero,
leading: const CircleAvatar(
backgroundColor: Color(0xFF1F2D4A),
child: Icon(Icons.person, color: Color(0xFF00E5FF)),
),
title: Text(admin['name'] ?? 'بدون اسم', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
subtitle: Text(admin['phone'] ?? 'بدون رقم', style: const TextStyle(color: Colors.grey)),
trailing: Text(
admin['created_at']?.split(' ')[0] ?? '',
style: const TextStyle(color: Colors.grey, fontSize: 12),
),
),
const Divider(color: Color(0xFF1F2D4A), height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => _handleAction(admin['id'], 'rejected'),
style: TextButton.styleFrom(foregroundColor: Colors.redAccent),
child: const Text('رفض'),
),
const SizedBox(width: 8),
ElevatedButton(
onPressed: () => _handleAction(admin['id'], 'approved'),
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFF00E5FF),
foregroundColor: Colors.black,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
child: const Text('موافقة', style: TextStyle(fontWeight: FontWeight.bold)),
),
],
),
],
),
);
},
);
}
}

View File

@@ -6,6 +6,7 @@ import '../../controller/auth/login_controller.dart';
import '../../controller/auth/otp_helper.dart';
import '../../controller/functions/crud.dart';
import '../../print.dart';
import '../admin/admin_home_page.dart';
// ─── Colors (نفس نظام الألوان المستخدم في التطبيق) ──────────────────────────
class _C {
@@ -31,30 +32,28 @@ class AdminLoginPage extends StatefulWidget {
class _AdminLoginPageState extends State<AdminLoginPage>
with SingleTickerProviderStateMixin {
final _phoneController = TextEditingController();
final _passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool _isLoading = false;
late final AnimationController _glowCtrl;
late final Animation<double> _glowAnim;
// ─── Logic (بدون تغيير) ────────────────────────────────────────────────────
Future<void> _submit() async {
final allowedPhones = Env.ALLOWED_ADMIN_PHONES;
Log.print('allowedPhones: ${allowedPhones}');
allowedPhones.toString().split(',');
final password = _passwordController.text.trim();
final phone = _phoneController.text.trim();
if (!allowedPhones.contains(phone)) {
Get.snackbar('رفض الدخول', 'رقم الهاتف غير مخوّل بالدخول إلى الإدارة');
if (password.isEmpty) {
Get.snackbar('خطأ', 'يرجى إدخال كلمة المرور');
return;
}
setState(() => _isLoading = true);
final otpSent = await OtpHelper.sendOtp(phone);
if (otpSent) {
Get.to(() => OtpVerificationAdmin(phone: phone));
final otpHelper = Get.find<OtpHelper>();
bool success = await otpHelper.loginWithPassword(password);
if (success) {
Get.offAll(() => const AdminHomePage());
}
setState(() => _isLoading = false);
@@ -75,7 +74,7 @@ class _AdminLoginPageState extends State<AdminLoginPage>
}
void _initializeToken() async {
await CRUD().getJWT();
// await CRUD().getJWT();
}
@override
@@ -168,7 +167,7 @@ class _AdminLoginPageState extends State<AdminLoginPage>
),
const SizedBox(height: 8),
const Text(
'أدخل رقم هاتفك للمتابعة',
'أدخل كلمة المرور للمتابعة',
style: TextStyle(
color: _C.textSec,
fontSize: 14,
@@ -195,14 +194,15 @@ class _AdminLoginPageState extends State<AdminLoginPage>
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// ── Field label ─────────────────────────────
const SizedBox(height: 20),
// ── Field label (Password) ─────────────────────────────
const Row(
children: [
Icon(Icons.phone_android_rounded,
Icon(Icons.lock_outline_rounded,
color: _C.accent, size: 16),
SizedBox(width: 8),
Text(
'رقم الهاتف',
'كلمة المرور',
style: TextStyle(
color: _C.textSec,
fontSize: 13,
@@ -213,28 +213,19 @@ class _AdminLoginPageState extends State<AdminLoginPage>
],
),
const SizedBox(height: 10),
// ── Phone field ─────────────────────────────
// ── Password field ─────────────────────────────
TextFormField(
controller: _phoneController,
keyboardType: TextInputType.phone,
textDirection: TextDirection.ltr,
controller: _passwordController,
obscureText: true,
style: const TextStyle(
color: _C.textPrimary,
fontSize: 16,
fontFamily: 'monospace',
letterSpacing: 1.2,
),
decoration: InputDecoration(
hintText: '+963 XXX XXX XXX',
hintStyle: const TextStyle(
color: _C.textSec,
fontSize: 14,
letterSpacing: 0.5),
hintText: '••••••••',
hintStyle: const TextStyle(color: _C.textSec),
filled: true,
fillColor: _C.inputBg,
prefixIcon: const Icon(Icons.dialpad_rounded,
color: _C.accentDim, size: 20),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: BorderSide.none,
@@ -249,24 +240,12 @@ class _AdminLoginPageState extends State<AdminLoginPage>
borderSide: const BorderSide(
color: _C.accent, width: 1.5),
),
errorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: _C.error, width: 1),
),
focusedErrorBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(14),
borderSide: const BorderSide(
color: _C.error, width: 1.5),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 16),
errorStyle: const TextStyle(
color: _C.error, fontSize: 12),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'الرجاء إدخال رقم الهاتف';
return 'الرجاء إدخال كلمة المرور';
}
return null;
},
@@ -296,6 +275,21 @@ class _AdminLoginPageState extends State<AdminLoginPage>
const SizedBox(height: 32),
// ── Register Button ─────────────────────────────────
TextButton(
onPressed: () => Get.toNamed('/register'),
child: const Text(
'ليس لديك حساب؟ طلب انضمام',
style: TextStyle(
color: _C.accent,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
const SizedBox(height: 16),
// ── Footer ──────────────────────────────────────────
Row(
mainAxisAlignment: MainAxisAlignment.center,
@@ -435,8 +429,8 @@ class _SubmitButtonState extends State<_SubmitButton>
children: [
Icon(Icons.send_rounded, color: Colors.white, size: 18),
SizedBox(width: 10),
Text(
'إرسال رمز التحقق',
const Text(
'تسجيل الدخول',
style: TextStyle(
color: Colors.white,
fontSize: 16,

View File

@@ -0,0 +1,181 @@
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';
class _C {
static const bg = Color(0xFF0A0D14);
static const card = Color(0xFF161D2E);
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();
}
@override
Widget build(BuildContext context) {
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),
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),
),
),
child: const Text('إرسال الطلب', style: TextStyle(fontWeight: FontWeight.bold)),
),
],
),
);
}
Widget _buildField(String label, TextEditingController ctrl, IconData icon,
{bool isPhone = false, bool isPass = false}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
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,
),
],
);
}
}