feat(apps): الدراور على نمط سيرو — المكافآت والدعوات والمساعدة

جُرد دراور سيرو: 14 مدخلاً للراكب (map_menu_widget) و23 للسائق
(drawer_captain). بُني ما له نقطة فعلية على NestJS، والباقي مذكور صراحةً.

## مبنيّ
- **المكافآت** (features/rewards): GET /rewards/referrals و /rewards/coupons —
  كلاهما مُتحقَّق حيّاً. كود الدعوة يُنسخ ويُشارك، وعدّادات دعوتَهم/قيد
  التأكيد/مدفوعة. يقابل Promos + Invite Driver في سيرو
- **شارك التطبيق · قيّم التطبيق · سياسة الخصوصية · تطبيق الطرف الآخر** —
  أفعال محليّة بلا نقطة خادم (share_plus + url_launcher)
- **المساعدة** وُسّعت: أسئلة شائعة قابلة للطيّ · واتساب الدعم ·
  **فحص توافق الجهاز** (نمط سيرو) يفحص ما يُعطّل التطبيق فعلاً: خدمة الموقع
  وإذنها وإذن الإشعارات — أسباب حقيقية لشكوى «التطبيق لا يعمل»
- روابط المتجر والخصوصية والدعم صارت قيم بناء لكل مستأجر في BuildConfig

## غير مبنيّ — ولا نقطة خادم له
الشكاوى (R3 في الباكلوج) · الإحصاءات · التحدّيات ولوحة المتصدّرين · مركز
الصيانة · جدولي · التأمين الصحي (مؤجَّل بقرار المالك) · الطعام والمواصلات
(خارج النطاق، docs/39 §1). بناء واجهة بلا خادم يعني شاشة تكذب على المستخدم.

## سجلّ الإشعارات
سيرو يعرض قائمة إشعارات؛ الخادم يوفّر POST /notifications/token فقط بلا نقطة
سرد. لم تُبنَ شاشة قائمة على فراغ.

flutter analyze نظيف في التطبيقين.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Hamza-Ayed
2026-08-05 01:34:23 +03:00
co-authored by Claude Opus 5
parent 6a33cc7241
commit ca766a2d4e
27 changed files with 1406 additions and 48 deletions
@@ -0,0 +1,65 @@
import '../../../core/api/api_client.dart';
/// ملخّص الدعوات — شكل مُتحقَّق حيّاً: `{code, total, pending, paid}`.
class ReferralSummary {
const ReferralSummary({
required this.code,
required this.total,
required this.pending,
required this.paid,
});
/// كود دعوتي الذي أشاركه.
final String code;
final int total;
final int pending;
final int paid;
factory ReferralSummary.fromJson(Map<String, dynamic> json) =>
ReferralSummary(
code: json['code'] as String? ?? '',
total: (json['total'] as num?)?.toInt() ?? 0,
pending: (json['pending'] as num?)?.toInt() ?? 0,
paid: (json['paid'] as num?)?.toInt() ?? 0,
);
}
class Coupon {
const Coupon({
required this.code,
this.description = '',
this.expiresAt,
});
final String code;
final String description;
final DateTime? expiresAt;
factory Coupon.fromJson(Map<String, dynamic> json) => Coupon(
code: json['code'] as String? ?? '',
description: json['description'] as String? ??
json['title'] as String? ??
'',
expiresAt: DateTime.tryParse(json['expires_at']?.toString() ?? ''),
);
}
class RewardsRepository {
RewardsRepository(this._api);
final ApiClient _api;
Future<ReferralSummary> referrals() async {
final res = await _api.get<Map<String, dynamic>>('/rewards/referrals');
return ReferralSummary.fromJson(res);
}
Future<List<Coupon>> coupons() async {
final res = await _api.get<List<dynamic>>('/rewards/coupons');
return res
.whereType<Map<String, dynamic>>()
.map(Coupon.fromJson)
.toList(growable: false);
}
}