From 7a576b732779bb50de85ade1d2f302c4f0f36ccf Mon Sep 17 00:00:00 2001 From: Hamza-Ayed Date: Thu, 30 Jul 2026 12:31:27 +0300 Subject: [PATCH] Update: 2026-07-30 12:31:26 --- .../loginUsingCredentialsWithoutGoogle.php | 63 +++--- .../loginUsingCredentialsWithoutGoogle.php | 29 ++- backend/core/Auth/RateLimiter.php | 2 +- backend/scripts/seed_tester_accounts.php | 214 ++++++++++++++++++ .../admin/financial/financial_v2_page.dart | 28 +-- .../auth/captin/login_captin_controller.dart | 7 +- .../lib/views/Rate/rate_passenger.dart | 3 +- .../payment_history_driver_page.dart | 19 +- .../lib/controller/auth/login_controller.dart | 19 +- .../points_page_for_rider.dart | 7 +- 10 files changed, 307 insertions(+), 84 deletions(-) create mode 100644 backend/scripts/seed_tester_accounts.php diff --git a/backend/auth/driver/loginUsingCredentialsWithoutGoogle.php b/backend/auth/driver/loginUsingCredentialsWithoutGoogle.php index 2ba7cccf..915f345e 100644 --- a/backend/auth/driver/loginUsingCredentialsWithoutGoogle.php +++ b/backend/auth/driver/loginUsingCredentialsWithoutGoogle.php @@ -11,18 +11,16 @@ $password = filterRequest('password'); $audience = filterRequest('aud') ?? 'siro-driver-android'; // الافتراضي $fingerprint = filterRequest('fingerPrint') ?? filterRequest('fingerprint'); -// 1. تطبيق حد معدل الطلبات (Rate Limiting) للفاحصين: 3 محاولات بالدقيقة لكل IP +// 1. حد معدل الطلبات مطبّق على الجميع (الحد مرفوع إلى 30/دقيقة في RateLimiter) $rateLimiter = new RateLimiter($redis); $rateLimiter->enforce(RateLimiter::identifier(), 'tester_login'); -if (!$email || !$password) { - echo json_encode(["status" => "failure", "message" => "Email and password are required"]); - exit(); -} - -// 2. التحقق من أن الحساب مخصص للفحص فقط (isTest check) -$allowedTesterEmailsEnv = getenv('ALLOWED_TESTER_EMAILS') ?: ''; -$allowedEmails = array_filter(array_map('trim', explode(',', $allowedTesterEmailsEnv))); +// 2. قائمة بيضاء صريحة لحسابات الفحص — مطابقة تامة فقط، لا مطابقة جزئية ولا مطابقة نطاق +$allowedTesterEmailsEnv = getenv('ALLOWED_TESTER_EMAILS') ?: ($_ENV['ALLOWED_TESTER_EMAILS'] ?? ''); +$allowedEmails = array_filter(array_map( + fn($e) => strtolower(trim($e)), + explode(',', $allowedTesterEmailsEnv) +)); if (empty($allowedEmails)) { $allowedEmails = [ 'driver_tester@siromove.com', @@ -30,24 +28,21 @@ if (empty($allowedEmails)) { ]; } -$cleanEmail = strtolower(trim($email)); -$isTester = in_array($cleanEmail, $allowedEmails) || - substr($cleanEmail, -13) === '@siromove.com' || - str_contains($cleanEmail, 'tester') || - str_contains($cleanEmail, 'reviewer'); +$cleanEmail = strtolower(trim((string) $email)); +$isTester = in_array($cleanEmail, $allowedEmails, true); -// تشفير الإيميل لاستخدامه في الاستعلام -$encryptedEmail = $encryptionHelper->encryptData($email); +if (!$email || !$password) { + echo json_encode(["status" => "failure", "message" => "Email and password are required"]); + exit(); +} try { $con = Database::get('main'); - - // Auto-seed/create tester driver logic removed for security + $encryptedEmail = $encryptionHelper->encryptData($email); global $blindIndex; $emailBidx = $blindIndex ? $blindIndex->index('driver.email', $email) : null; - // SQL لاسترجاع المستخدم بناءً على البريد الإلكتروني المشفر أو الفهرس الأعمى $sql = "SELECT driver.*, phone_verification.is_verified, @@ -67,36 +62,30 @@ try { $data = $stmt->fetch(PDO::FETCH_ASSOC); if ($data) { - // التحقق من أن الحساب معلم كحساب فحص في قاعدة البيانات أو البيئة $isTestInDb = (isset($data['is_test']) && $data['is_test'] == 1) || (isset($data['isTest']) && $data['isTest'] == 1); if (!$isTestInDb && !$isTester) { jsonError("Access denied. Not a tester account."); exit(); } - // فحص الباسورد (في نظامنا، يمكن أن يكون الباسورد هو HMAC أو نص عادي للفاحصين) - // لنفترض أن الفاحص له باسورد عادي أو مشفر بـ bcrypt - if (password_verify($password, $data['password'])) { + + if (password_verify($password, $data['password'] ?? '')) { unset($data['password']); - // فك تشفير الحقول الحساسة - $data['phone'] = $encryptionHelper->decryptData($data['phone']); - $data['email'] = $encryptionHelper->decryptData($data['email']); - $data['gender'] = $encryptionHelper->decryptData($data['gender']); - $data['birthdate'] = $encryptionHelper->decryptData($data['birthdate']); - $data['site'] = $encryptionHelper->decryptData($data['site']); - $data['first_name'] = $encryptionHelper->decryptData($data['first_name']); - $data['last_name'] = $encryptionHelper->decryptData($data['last_name']); - if(isset($data['employmentType'])) $data['employmentType'] = $encryptionHelper->decryptData($data['employmentType']); - if(isset($data['maritalStatus'])) $data['maritalStatus'] = $encryptionHelper->decryptData($data['maritalStatus']); + if(isset($data['phone'])) $data['phone'] = $encryptionHelper->decryptData($data['phone']); + if(isset($data['email'])) $data['email'] = $encryptionHelper->decryptData($data['email']); + if(isset($data['gender'])) $data['gender'] = $encryptionHelper->decryptData($data['gender']); + if(isset($data['birthdate'])) $data['birthdate'] = $encryptionHelper->decryptData($data['birthdate']); + if(isset($data['site'])) $data['site'] = $encryptionHelper->decryptData($data['site']); + if(isset($data['first_name'])) $data['first_name'] = $encryptionHelper->decryptData($data['first_name']); + if(isset($data['last_name'])) $data['last_name'] = $encryptionHelper->decryptData($data['last_name']); - // توليد الـ JWT بصلاحية (tester) لتميزهم عن السائقين الفعليين $jwtService = new JwtService($redis); $jwt = $jwtService->generateAccessToken($data['id'], 'tester', $audience, $fingerprint); echo json_encode([ "status" => "success", "jwt" => $jwt, - "data" => [$data] // مطابق لنسق التطبيق الذي يتوقع مصفوفة + "data" => [$data] ], JSON_UNESCAPED_UNICODE); } else { jsonError("Incorrect password."); @@ -104,8 +93,8 @@ try { } else { jsonError("User does not exist."); } -} catch (Exception $e) { - error_log("[Tester Login Error] " . $e->getMessage()); +} catch (Throwable $e) { + error_log("[Tester Login Error] " . $e->getMessage() . " in " . $e->getFile() . ":" . $e->getLine()); jsonError("Server error occurred."); } finally { $stmt = null; diff --git a/backend/auth/passenger/loginUsingCredentialsWithoutGoogle.php b/backend/auth/passenger/loginUsingCredentialsWithoutGoogle.php index 345e13a1..093721e5 100644 --- a/backend/auth/passenger/loginUsingCredentialsWithoutGoogle.php +++ b/backend/auth/passenger/loginUsingCredentialsWithoutGoogle.php @@ -9,18 +9,16 @@ $password = filterRequest("password"); $fingerprint = filterRequest('fingerPrint') ?? filterRequest('fingerprint'); $audience = filterRequest('aud') ?: 'siro_passenger'; -// 1. تطبيق حد معدل الطلبات (Rate Limiting) للفاحصين: 3 محاولات بالدقيقة لكل IP +// 1. حد معدل الطلبات مطبّق على الجميع (الحد مرفوع إلى 30/دقيقة في RateLimiter) $rateLimiter = new RateLimiter($redis); $rateLimiter->enforce(RateLimiter::identifier(), 'tester_login'); -if (!$email || !$password) { - echo json_encode(["status" => "failure", "message" => "Email and password are required"]); - exit(); -} - -// 2. التحقق من أن الحساب مخصص للفحص فقط (isTest check) -$allowedTesterEmailsEnv = getenv('ALLOWED_TESTER_EMAILS') ?: ''; -$allowedEmails = array_filter(array_map('trim', explode(',', $allowedTesterEmailsEnv))); +// 2. قائمة بيضاء صريحة لحسابات الفحص — مطابقة تامة فقط، لا مطابقة جزئية ولا مطابقة نطاق +$allowedTesterEmailsEnv = getenv('ALLOWED_TESTER_EMAILS') ?: ($_ENV['ALLOWED_TESTER_EMAILS'] ?? ''); +$allowedEmails = array_filter(array_map( + fn($e) => strtolower(trim($e)), + explode(',', $allowedTesterEmailsEnv) +)); if (empty($allowedEmails)) { $allowedEmails = [ 'driver_tester@siromove.com', @@ -28,12 +26,13 @@ if (empty($allowedEmails)) { ]; } +$cleanEmail = strtolower(trim((string) $email)); +$isTester = in_array($cleanEmail, $allowedEmails, true); -$cleanEmail = strtolower(trim($email)); -$isTester = in_array($cleanEmail, $allowedEmails) || - substr($cleanEmail, -13) === '@siromove.com' || - str_contains($cleanEmail, 'tester') || - str_contains($cleanEmail, 'reviewer'); +if (!$email || !$password) { + echo json_encode(["status" => "failure", "message" => "Email and password are required"]); + exit(); +} try { $con = Database::get('main'); @@ -121,7 +120,7 @@ try { http_response_code(500); echo json_encode([ "status" => "failure", - "message" => "Server error: " . $e->getMessage() . " in " . basename($e->getFile()) . " on line " . $e->getLine() + "message" => "Server error. Please try again." ]); } exit(); diff --git a/backend/core/Auth/RateLimiter.php b/backend/core/Auth/RateLimiter.php index 838228da..bf501a8e 100644 --- a/backend/core/Auth/RateLimiter.php +++ b/backend/core/Auth/RateLimiter.php @@ -11,7 +11,7 @@ class RateLimiter // حدود مختلفة لكل نوع endpoint private const LIMITS = [ 'login' => ['requests' => 5, 'window' => 60], // 5 محاولات / دقيقة - 'tester_login' => ['requests' => 3, 'window' => 60], // 3 محاولات / دقيقة + 'tester_login' => ['requests' => 30, 'window' => 60], // 30 محاولة / دقيقة (مراجعو المتاجر يكرّرون الدخول بسرعة) 'otp' => ['requests' => 3, 'window' => 300], // 3 محاولات / 5 دقائق 'register' => ['requests' => 3, 'window' => 3600], // 3 محاولات / ساعة 'api' => ['requests' => 180, 'window' => 60], // 180 طلب / دقيقة (الإنتاج الرسمى) diff --git a/backend/scripts/seed_tester_accounts.php b/backend/scripts/seed_tester_accounts.php new file mode 100644 index 00000000..0d87dfe1 --- /dev/null +++ b/backend/scripts/seed_tester_accounts.php @@ -0,0 +1,214 @@ + strtolower(trim($e)), + explode(',', $allowedEnv) +))); +if (empty($allowed)) { + $allowed = ['driver_tester@siromove.com', 'passenger_tester@siromove.com']; +} + +$passengerEmail = null; +$driverEmail = null; +foreach ($allowed as $e) { + if ($driverEmail === null && str_contains($e, 'driver')) { + $driverEmail = $e; + } elseif ($passengerEmail === null) { + $passengerEmail = $e; + } +} +if (!$passengerEmail || !$driverEmail) { + exit("✘ ALLOWED_TESTER_EMAILS يجب أن يحتوي بريد سائق (يتضمن 'driver') وبريد راكب.\n"); +} + +$passengerPhone = '+963900000000'; +$driverPhone = '+963900000001'; + +$con = Database::get('main'); + +echo ($dryRun ? "— وضع المعاينة (لا كتابة) —\n" : "— تنفيذ فعلي —\n"); + +// ── الراكب ──────────────────────────────────────────────── +$emailEnc = $encryptionHelper->encryptData($passengerEmail); +$emailBidx = $blindIndex ? $blindIndex->index('passengers.email', $passengerEmail) : null; +$hash = password_hash($passengerPassword, PASSWORD_BCRYPT); + +$stmt = $con->prepare( + "SELECT id FROM passengers WHERE email = ? OR (? IS NOT NULL AND email_bidx = ?) LIMIT 1" +); +$stmt->execute([$emailEnc, $emailBidx, $emailBidx]); +$existing = $stmt->fetchColumn(); + +$phoneKey = otpPhoneKey($passengerPhone); + +if ($existing) { + echo "• الراكب $passengerEmail موجود (id=$existing) — إعادة تعيين الباسورد.\n"; + if (!$dryRun) { + $con->prepare("UPDATE passengers SET password = ?, updated_at = NOW() WHERE id = ?") + ->execute([$hash, $existing]); + } + $passengerId = $existing; +} else { + $passengerId = substr(md5(uniqid((string) mt_rand(), true)), 0, 20); + echo "• إنشاء الراكب $passengerEmail (id=$passengerId).\n"; + if (!$dryRun) { + $unknown = $encryptionHelper->encryptData('unknown'); + $con->prepare(" + INSERT INTO passengers + (id, first_name, last_name, email, phone, password, gender, birthdate, site, + sosPhone, education, employmentType, maritalStatus, status, created_at, updated_at, + phone_bidx, email_bidx, name_bidx, phone_key) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active', NOW(), NOW(), ?, ?, ?, ?) + ")->execute([ + $passengerId, + $encryptionHelper->encryptData('Siro'), + $encryptionHelper->encryptData('Tester'), + $emailEnc, + $encryptionHelper->encryptData($passengerPhone), + $hash, + $unknown, $unknown, $unknown, $unknown, $unknown, $unknown, $unknown, + $blindIndex ? $blindIndex->index('passengers.phone', $passengerPhone) : null, + $emailBidx, + $blindIndex ? $blindIndex->index('passengers.name', 'Siro Tester') : null, + $phoneKey, + ]); + } +} + +// سجل تحقق الهاتف للراكب — الدخول يقرأ verified من هذا الجدول +$stmt = $con->prepare("SELECT id FROM phone_verification_passenger WHERE phone_number = ? LIMIT 1"); +$stmt->execute([$phoneKey]); +$verifRow = $stmt->fetchColumn(); +if ($verifRow) { + echo " ↳ تحديث سجل التحقق (verified = 1).\n"; + if (!$dryRun) { + $con->prepare("UPDATE phone_verification_passenger SET verified = 1, status = 'verified' WHERE id = ?") + ->execute([$verifRow]); + } +} else { + echo " ↳ إنشاء سجل التحقق (verified = 1).\n"; + if (!$dryRun) { + $con->prepare(" + INSERT INTO phone_verification_passenger (phone_number, verified, status, created_at) + VALUES (?, 1, 'verified', NOW()) + ")->execute([$phoneKey]); + } +} + +// ── السائق ──────────────────────────────────────────────── +$dEmailEnc = $encryptionHelper->encryptData($driverEmail); +$dEmailBidx = $blindIndex ? $blindIndex->index('driver.email', $driverEmail) : null; +$dHash = password_hash($driverPassword, PASSWORD_BCRYPT); + +$stmt = $con->prepare( + "SELECT id FROM driver WHERE email = ? OR (? IS NOT NULL AND email_bidx = ?) LIMIT 1" +); +$stmt->execute([$dEmailEnc, $dEmailBidx, $dEmailBidx]); +$existingDriver = $stmt->fetchColumn(); + +$dPhoneKey = otpPhoneKey($driverPhone); + +if ($existingDriver) { + echo "• السائق $driverEmail موجود (id=$existingDriver) — إعادة تعيين الباسورد.\n"; + if (!$dryRun) { + $con->prepare("UPDATE driver SET password = ?, updated_at = NOW() WHERE id = ?") + ->execute([$dHash, $existingDriver]); + } + $driverId = $existingDriver; +} else { + $driverId = substr(md5(uniqid((string) mt_rand(), true)), 0, 20); + echo "• إنشاء السائق $driverEmail (id=$driverId).\n"; + if (!$dryRun) { + $con->prepare(" + INSERT INTO driver + (id, phone, email, password, gender, license_type, national_number, name_arabic, + issue_date, expiry_date, license_categories, address, licenseIssueDate, status, + birthdate, site, first_name, last_name, created_at, updated_at, + phone_bidx, email_bidx, name_bidx, phone_key) + VALUES (?, ?, ?, ?, 'Male', 'private', ?, ?, '2020-01-01', '2030-01-01', 'B', + 'Damascus', '2020-01-01', 'notDeleted', ?, ?, ?, ?, NOW(), NOW(), ?, ?, ?, ?) + ")->execute([ + $driverId, + $encryptionHelper->encryptData($driverPhone), + $dEmailEnc, + $dHash, + $encryptionHelper->encryptData('00000000'), + 'سيرو فاحص', + $encryptionHelper->encryptData('1990-01-01'), + $encryptionHelper->encryptData('Damascus'), + $encryptionHelper->encryptData('Siro'), + $encryptionHelper->encryptData('Captain'), + $blindIndex ? $blindIndex->index('driver.phone', $driverPhone) : null, + $dEmailBidx, + $blindIndex ? $blindIndex->index('driver.name', 'Siro Captain') : null, + $dPhoneKey, + ]); + } +} + +// سجل تحقق الهاتف للسائق +$stmt = $con->prepare("SELECT id FROM phone_verification WHERE phone_number = ? LIMIT 1"); +$stmt->execute([$dPhoneKey]); +$dVerifRow = $stmt->fetchColumn(); +if ($dVerifRow) { + echo " ↳ تحديث سجل التحقق (is_verified = 1).\n"; + if (!$dryRun) { + $con->prepare("UPDATE phone_verification SET is_verified = 1, driverId = ? WHERE id = ?") + ->execute([$driverId, $dVerifRow]); + } +} else { + echo " ↳ إنشاء سجل التحقق (is_verified = 1).\n"; + if (!$dryRun) { + $con->prepare(" + INSERT INTO phone_verification (phone_number, driverId, email, is_verified, created_at) + VALUES (?, ?, ?, 1, NOW()) + ")->execute([$dPhoneKey, $driverId, $dEmailEnc]); + } +} + +echo "\n✔ تم." . ($dryRun ? " (معاينة فقط — أعد التشغيل دون --dry-run للكتابة)" : "") . "\n"; +echo "سلّم للمتجر: $passengerEmail و $driverEmail مع كلمتَي المرور المستخدمتين أعلاه.\n"; diff --git a/siro_admin/lib/views/admin/financial/financial_v2_page.dart b/siro_admin/lib/views/admin/financial/financial_v2_page.dart index df282836..bd1040b8 100644 --- a/siro_admin/lib/views/admin/financial/financial_v2_page.dart +++ b/siro_admin/lib/views/admin/financial/financial_v2_page.dart @@ -27,11 +27,16 @@ class FinancialV2Page extends StatelessWidget { borderRadius: BorderRadius.circular(10), border: Border.all(color: cs.outline), ), - child: Icon(Icons.arrow_back_ios_new_rounded, color: cs.onSurfaceVariant, size: 16), + child: Icon(Icons.arrow_back_ios_new_rounded, + color: cs.onSurfaceVariant, size: 16), ), ), const SizedBox(width: 12), - Text('الإدارة المالية المتقدمة', style: TextStyle(color: cs.onSurface, fontSize: 18, fontWeight: FontWeight.w700)), + Text('الإدارة المالية المتقدمة', + style: TextStyle( + color: cs.onSurface, + fontSize: 18, + fontWeight: FontWeight.w700)), const Spacer(), IconButton( icon: Icon(Icons.refresh_rounded, color: cs.onSurfaceVariant), @@ -157,8 +162,7 @@ class FinancialV2Page extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(title, - style: TextStyle( - color: cs.onSurfaceVariant, fontSize: 12)), + style: TextStyle(color: cs.onSurfaceVariant, fontSize: 12)), const SizedBox(height: 4), Text(value, style: TextStyle( @@ -173,7 +177,8 @@ class FinancialV2Page extends StatelessWidget { ); } - Widget _buildPaymentMethodBreakdown(Map stats, ColorScheme cs) { + Widget _buildPaymentMethodBreakdown( + Map stats, ColorScheme cs) { double cash = double.tryParse(stats['cash_payments'].toString()) ?? 0; double digital = double.tryParse(stats['digital_payments'].toString()) ?? 0; double total = cash + digital; @@ -204,8 +209,7 @@ class FinancialV2Page extends StatelessWidget { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(label, - style: TextStyle(color: cs.onSurface, fontSize: 13)), + Text(label, style: TextStyle(color: cs.onSurface, fontSize: 13)), Text('${value.toStringAsFixed(0)} ج.م', style: TextStyle(color: color, fontWeight: FontWeight.bold)), ], @@ -253,15 +257,13 @@ class FinancialV2Page extends StatelessWidget { children: [ Text('${s['first_name']} ${s['last_name']}', style: TextStyle( - color: cs.onSurface, - fontWeight: FontWeight.bold)), + color: cs.onSurface, fontWeight: FontWeight.bold)), Text(s['phone'] ?? '', style: TextStyle( color: cs.onSurfaceVariant, fontSize: 12)), const SizedBox(height: 4), Text('${s['total_rides']} رحلة مكتملة', - style: TextStyle( - color: cs.tertiary, fontSize: 11)), + style: TextStyle(color: cs.tertiary, fontSize: 11)), ], ), ), @@ -269,8 +271,8 @@ class FinancialV2Page extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ Text('المستحقات', - style: TextStyle( - color: cs.onSurfaceVariant, fontSize: 10)), + style: + TextStyle(color: cs.onSurfaceVariant, fontSize: 10)), Text('${s['total_earned']} ج.م', style: TextStyle( color: cs.primary, diff --git a/siro_driver/lib/controller/auth/captin/login_captin_controller.dart b/siro_driver/lib/controller/auth/captin/login_captin_controller.dart index 00c6eaa9..ed393b13 100755 --- a/siro_driver/lib/controller/auth/captin/login_captin_controller.dart +++ b/siro_driver/lib/controller/auth/captin/login_captin_controller.dart @@ -680,10 +680,15 @@ class LoginDriverController extends GetxController { Get.off(() => HomeCaptain()); } else { - mySnackbarError('Login failed'.tr); + mySnackbarError( + (jsonDecoeded['message'] ?? 'Login failed').toString().tr); isloading = false; update(); } + } else if (response.statusCode == 429) { + mySnackbarError('Too many attempts. Please wait a minute.'.tr); + isloading = false; + update(); } else { mySnackbarError('Server error'.tr); isloading = false; diff --git a/siro_driver/lib/views/Rate/rate_passenger.dart b/siro_driver/lib/views/Rate/rate_passenger.dart index ebce1725..15f8a931 100755 --- a/siro_driver/lib/views/Rate/rate_passenger.dart +++ b/siro_driver/lib/views/Rate/rate_passenger.dart @@ -119,7 +119,8 @@ class RatePassenger extends StatelessWidget { Padding( padding: const EdgeInsets.only(top: 8.0), child: Text( - CurrencyHelper.currency, // Replace with your local currency symbol if needed + CurrencyHelper + .currency, // Replace with your local currency symbol if needed style: TextStyle( color: Colors.white.withOpacity(0.8), fontSize: 24, diff --git a/siro_driver/lib/views/home/my_wallet/payment_history_driver_page.dart b/siro_driver/lib/views/home/my_wallet/payment_history_driver_page.dart index 8dfec192..1ec94560 100755 --- a/siro_driver/lib/views/home/my_wallet/payment_history_driver_page.dart +++ b/siro_driver/lib/views/home/my_wallet/payment_history_driver_page.dart @@ -17,12 +17,15 @@ class PaymentHistoryDriverPage extends StatelessWidget { backgroundColor: FinanceDesignSystem.backgroundColor, appBar: AppBar( title: Text('Payment History'.tr, - style: TextStyle(fontWeight: FontWeight.bold, color: FinanceDesignSystem.primaryDark)), + style: TextStyle( + fontWeight: FontWeight.bold, + color: FinanceDesignSystem.primaryDark)), backgroundColor: Colors.transparent, elevation: 0, centerTitle: true, leading: IconButton( - icon: Icon(Icons.arrow_back_ios_new_rounded, color: FinanceDesignSystem.primaryDark, size: 20), + icon: Icon(Icons.arrow_back_ios_new_rounded, + color: FinanceDesignSystem.primaryDark, size: 20), onPressed: () => Get.back(), ), ), @@ -37,10 +40,13 @@ class PaymentHistoryDriverPage extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Icon(Icons.history_rounded, size: 80, color: Colors.grey.shade300), + Icon(Icons.history_rounded, + size: 80, color: Colors.grey.shade300), const SizedBox(height: 16), Text('No transactions yet'.tr, - style: TextStyle(color: Colors.grey.shade400, fontWeight: FontWeight.bold)), + style: TextStyle( + color: Colors.grey.shade400, + fontWeight: FontWeight.bold)), ], ), ); @@ -52,8 +58,9 @@ class PaymentHistoryDriverPage extends StatelessWidget { itemCount: controller.archive.length, itemBuilder: (BuildContext context, int index) { final tx = controller.archive[index]; - final double amount = double.tryParse(tx['amount']?.toString() ?? '0') ?? 0; - + final double amount = + double.tryParse(tx['amount']?.toString() ?? '0') ?? 0; + return AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 375), diff --git a/siro_rider/lib/controller/auth/login_controller.dart b/siro_rider/lib/controller/auth/login_controller.dart index 84ad6f6e..d82386a2 100644 --- a/siro_rider/lib/controller/auth/login_controller.dart +++ b/siro_rider/lib/controller/auth/login_controller.dart @@ -12,7 +12,6 @@ import 'package:http/http.dart' as http; import 'package:siro_rider/constant/info.dart'; import 'package:siro_rider/controller/functions/add_error.dart'; -import 'package:siro_rider/views/auth/login_page.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -482,7 +481,7 @@ class LoginController extends GetxController { 'aud': '${AK.allowed}${Platform.isAndroid ? 'android' : 'ios'}', }; Log.print( - "Tester Login Payload: email=${payload['email']}, password=${payload['password']}, aud=${payload['aud']}"); + "Tester Login Payload: email=${payload['email']}, aud=${payload['aud']}"); var response = await http.post( Uri.parse(AppLink.loginUsingCredentialsWithoutGooglePassenger), @@ -525,19 +524,27 @@ class LoginController extends GetxController { Get.offAll(() => const MapPagePassenger()); } else { Log.print( - "Tester Login Failed due to condition mismatch: status=${jsonDecoeded['status']}, verified=${jsonDecoeded['data']?[0]?['verified']}"); - Get.offAll(() => LoginPage()); + "Tester Login Condition mismatch: status=${jsonDecoeded['status']}, verified=${jsonDecoeded['data']?[0]?['verified']}"); + mySnackbarError( + (jsonDecoeded['message'] ?? 'Login failed').toString().tr); isloading = false; update(); } + } else if (response.statusCode == 429) { + Log.print("Tester Login rate limited: ${response.body}"); + mySnackbarError('Too many attempts. Please wait a minute.'.tr); + isloading = false; + update(); } else { Log.print( - "Tester Login Failed with status code: ${response.statusCode}, body: ${response.body}"); + "Tester Login HTTP error: ${response.statusCode}, body: ${response.body}"); + mySnackbarError('Server error'.tr); isloading = false; update(); } } catch (e) { - Log.print("Tester Login Error: $e"); + Log.print("Tester Login Exception: $e"); + mySnackbarError('Network error'.tr); isloading = false; update(); } diff --git a/siro_rider/lib/views/home/map_widget.dart/points_page_for_rider.dart b/siro_rider/lib/views/home/map_widget.dart/points_page_for_rider.dart index fc6497ea..1d64828a 100644 --- a/siro_rider/lib/views/home/map_widget.dart/points_page_for_rider.dart +++ b/siro_rider/lib/views/home/map_widget.dart/points_page_for_rider.dart @@ -56,8 +56,7 @@ class PointsPageForRider extends StatelessWidget { wayPointController.wayPoints.length > 1 ? ElevatedButton( onPressed: () async { - locationSearch - .getMapPointsForAllMethods(); + locationSearch.getMapPointsForAllMethods(); }, child: const Text('Get Direction'), ) @@ -108,8 +107,8 @@ class PointsPageForRider extends StatelessWidget { child: Container( decoration: BoxDecoration( border: Border.all(), - color: - AppColor.accentColor.withValues(alpha: 0.5)), + color: AppColor.accentColor + .withValues(alpha: 0.5)), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween,