Commit 0da841fd authored by Mahmoud Aglan's avatar Mahmoud Aglan

Rewrite mobile app plan — per-instance architecture + Firebase + demo auth

- One app per client: single instance.dart config file change = new build
- Firebase: one project per client, service account JSON uploaded to system settings
- Auth: demo mode (OTP=123456) for testing, real SMS when provider configured
- Runtime branding: logo/colors fetched from /api/v1/app/config, not hardcoded
- Full Firebase setup checklist, notification flow diagram, deployment checklist
- New client deployment: Firebase (5min) + Flutter build (5min) + config (2min)
Co-Authored-By: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
parent 733ed4d3
...@@ -9,20 +9,345 @@ A visually stunning, data-driven mobile app for guardians and participants. Push ...@@ -9,20 +9,345 @@ A visually stunning, data-driven mobile app for guardians and participants. Push
--- ---
## Per-Instance Architecture (ONE APP PER CLIENT)
### The Model
Each client (academy) gets their own app uploaded to Play Store / App Store under their brand. The Flutter source code is IDENTICAL across all builds — the only difference is a single config file that points to the instance URL.
### How It Works
```
flutter_app/
├── lib/
│ ├── config/
│ │ └── instance.dart ← THE ONLY FILE YOU CHANGE PER CLIENT
│ ├── core/
│ ├── features/
│ └── main.dart
├── android/
│ ├── app/src/main/
│ │ └── google-services.json ← Per-client Firebase config (Android)
│ └── ...
├── ios/
│ ├── Runner/
│ │ └── GoogleService-Info.plist ← Per-client Firebase config (iOS)
│ └── ...
└── assets/
└── branding/ ← Optional: splash screen override
```
### instance.dart — The Single Source of Truth
```dart
class InstanceConfig {
// ===== CHANGE THESE PER CLIENT =====
static const String baseUrl = 'https://oc-sport.caprover.al-arcade.com';
static const String appName = 'OC Sport';
static const String appNameAr = 'او سي سبورت';
static const String packageId = 'com.alcaptain.ocsport'; // unique per Play Store listing
// ====================================
// These are fetched from the API at runtime — no need to hardcode
// Logo, colors, branding → GET /api/v1/app/config
}
```
### What Happens at App Startup
```
1. App reads instance.dart → knows the base URL
2. App calls GET {baseUrl}/api/v1/app/config (public, no auth)
3. Server returns:
- academy name (ar + en)
- logo URL
- primary color / accent color / theme
- feature flags (shop enabled? events enabled? chat enabled?)
- minimum app version (for forced updates)
- maintenance mode flag
4. App caches this config locally (refreshes every 24h or on app open)
5. App renders splash + login screen with the academy's branding
```
### To Deploy for a New Client
1. Clone the Flutter repo
2. Edit `lib/config/instance.dart` → paste the instance URL + package ID
3. Drop in `google-services.json` (from their Firebase project)
4. Run `flutter build apk --release` (or `appbundle`)
5. Upload to Play Store under the client's developer account
6. On the Laravel instance: add the Firebase service account JSON to system settings
7. Done — notifications flow from their instance to their app
---
## Authentication — Demo Mode (No SMS)
### The Problem
We don't have an SMS provider yet. We need the app to be testable and demo-able without real OTP delivery.
### Solution: Dual-Mode Auth
```
System Setting: `auth_otp_mode` = 'sms' | 'demo'
Mode: 'demo' (default for new instances)
- OTP is always 123456
- No SMS sent
- Console/log shows the OTP
- Works for prototyping, internal testing, demos
- Yellow banner in app: "وضع تجريبي — رمز التحقق: 123456"
Mode: 'sms' (production)
- Real OTP generated (6 random digits)
- Sent via configured SMS provider
- 5-minute TTL in cache
- Rate limited: 3 attempts per phone per 10 min
```
### SMS Provider Integration (when ready)
```php
// config/services.php
'sms' => [
'provider' => env('SMS_PROVIDER', 'log'), // 'log', 'twilio', 'vonage', 'smsmisr', 'victorylink'
'from' => env('SMS_FROM', 'ElCaptain'),
// Provider-specific keys in .env
],
// Providers for Egypt:
// - SMS Misr (smsmisr.com) — cheapest local, Arabic support
// - Victory Link — popular in Egypt
// - Twilio — international, reliable, expensive
// - Vonage — international alternative
```
### Auth Flow (Complete)
```
Phone Input → POST /api/v1/auth/otp/request {phone: "+201012345678"}
├── demo mode: cache OTP as "123456", return {sent: true, demo: true}
└── sms mode: generate random 6-digit, send SMS, cache 5min, return {sent: true}
OTP Input → POST /api/v1/auth/otp/verify {phone, otp}
├── verify against cache
├── find User by phone → find linked Participant/Guardian
├── issue Sanctum token with abilities ['mobile:*']
└── return {token, user, participants: [...]}
Subsequent requests → Bearer {token} header
├── token valid 90 days
├── refreshed on each API call (sliding expiry)
└── revoked on logout or password change
```
---
## Firebase & Push Notifications — Complete Setup
### Architecture: One Firebase Project Per Client
Each client gets their own Firebase project. This keeps:
- Push notifications branded per academy
- Analytics separated
- No cross-client data leakage
- Client can own their own Firebase project if they want
### What You Need Per Client (Firebase Console)
1. **Create Firebase Project** → name it after the academy (e.g., "OC Sport")
2. **Add Android App** → package name matches `instance.dart` packageId
3. **Download `google-services.json`** → goes in `android/app/`
4. **(iOS) Add iOS App** → bundle ID matches → download `GoogleService-Info.plist`
5. **Cloud Messaging** → enabled by default
6. **Create Service Account** for server-side sending:
- Firebase Console → Project Settings → Service accounts
- Click "Generate new private key"
- Download the JSON file (this is what the Laravel instance needs)
### What The Laravel Instance Needs
```
System Settings (stored in DB, academy-level):
- firebase_service_account_json: (the full JSON content from step 6 above)
OR stored as a file:
- storage/app/firebase/service-account.json
Environment variable alternative:
- FIREBASE_CREDENTIALS=/path/to/service-account.json
```
### How Notifications Flow
```
[Event in System] [Laravel Instance] [FCM] [Phone]
│ │ │ │
│ e.g., Attendance Marked │ │ │
├─────────────────────────────────────>│ │ │
│ │ PushNotificationService│ │
│ │ loads service account │ │
│ │ gets device tokens │ │
│ │ from device_tokens DB │ │
│ ├────────────────────────>│ │
│ │ FCM HTTP v1 API call │ │
│ │ POST https://fcm.googleapis.com/v1/ │
│ │ projects/{project}/messages:send │
│ │ ├────────────────────>│
│ │ │ Push delivered │
│ │ │ (even if app closed)
```
### Firebase Configuration Checklist Per Client
| Step | Where | What |
|------|-------|------|
| 1 | Firebase Console | Create project |
| 2 | Firebase Console | Add Android app with correct package name |
| 3 | Firebase Console | Download `google-services.json` |
| 4 | Flutter repo | Place `google-services.json` in `android/app/` |
| 5 | Firebase Console | (iOS) Add iOS app, download plist |
| 6 | Flutter repo | (iOS) Place `GoogleService-Info.plist` in `ios/Runner/` |
| 7 | Firebase Console | Project Settings → Service Accounts → Generate private key |
| 8 | Laravel instance | Upload service account JSON via System Settings UI |
| 9 | Laravel instance | Done — system can now push to this client's app |
### Laravel Side: `PushNotificationService`
```php
// Uses: kreait/firebase-php (official Firebase Admin SDK)
// composer require kreait/firebase-php
class PushNotificationService
{
private Messaging $messaging;
public function __construct()
{
$credentials = $this->getCredentials(); // from system_settings or file
$factory = (new Factory)->withServiceAccount($credentials);
$this->messaging = $factory->createMessaging();
}
public function sendToUser(User $user, string $title, string $body, array $data = []): void
{
$tokens = DeviceToken::where('user_id', $user->id)
->where('is_active', true)
->pluck('device_token')
->toArray();
if (empty($tokens)) return;
$message = CloudMessage::new()
->withNotification(Notification::create($title, $body))
->withData($data); // custom payload for in-app routing
$report = $this->messaging->sendMulticast($message, $tokens);
// Deactivate invalid tokens
foreach ($report->invalidTokens() as $token) {
DeviceToken::where('device_token', $token)->update(['is_active' => false]);
}
}
public function sendToParticipantGuardians(Participant $participant, string $title, string $body, array $data = []): void
{
$guardianUserIds = $participant->guardians()->pluck('user_id')->filter();
foreach ($guardianUserIds as $userId) {
$this->sendToUser(User::find($userId), $title, $body, $data);
}
}
}
```
### FCM Message Payload Structure
```json
{
"message": {
"notification": {
"title": "تم تسجيل الحضور",
"body": "تم تسجيل حضور أحمد محمد في جلسة اليوم"
},
"data": {
"type": "attendance_marked",
"participant_uuid": "abc-123",
"session_id": "456",
"click_action": "OPEN_ATTENDANCE"
},
"android": {
"priority": "high",
"notification": {
"channel_id": "attendance",
"sound": "default"
}
},
"apns": {
"payload": {
"aps": {
"sound": "default",
"badge": 1
}
}
}
}
}
```
### Flutter Side: FCM Setup
```dart
// pubspec.yaml
dependencies:
firebase_core: ^latest
firebase_messaging: ^latest
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
// Request notification permission (iOS)
await FirebaseMessaging.instance.requestPermission();
// Get FCM token → send to backend
final token = await FirebaseMessaging.instance.getToken();
// POST /api/v1/devices/register {token, platform: 'android'}
// Listen for token refresh
FirebaseMessaging.instance.onTokenRefresh.listen((newToken) {
// PATCH /api/v1/devices/refresh {old_token, new_token}
});
// Handle background messages
FirebaseMessaging.onBackgroundMessage(_backgroundHandler);
runApp(MyApp());
}
@pragma('vm:entry-point')
Future<void> _backgroundHandler(RemoteMessage message) async {
// This runs even when app is terminated
// Handle: badge count, local storage update, etc.
}
```
---
## Features (Client-Facing) ## Features (Client-Facing)
### 1. Authentication & Onboarding ### 1. Authentication & Onboarding
- Phone number + OTP login (Egypt: +20) - Phone number + OTP login (Egypt: +20)
- Link guardian to existing account (by phone or national ID) - Demo mode: fixed OTP 123456, yellow banner
- Link guardian to existing account (by phone)
- Participant self-login (age 14+) via phone - Participant self-login (age 14+) via phone
- Biometric (fingerprint/face) for quick re-entry - Biometric (fingerprint/face) for quick re-entry
- Academy branding on login screen (logo, colors from website_settings) - Academy branding on login screen (fetched from API, not hardcoded)
### 2. Dashboard (Home Screen) ### 2. Dashboard (Home Screen)
- Upcoming sessions (today/this week) with countdown timers - Upcoming sessions (today/this week) with countdown timers
- Outstanding balance / next payment due - Outstanding balance / next payment due
- Recent attendance (last 5 sessions — present/absent indicators) - Recent attendance (last 5 sessions — present/absent indicators)
- Academy announcements (from notification_templates) - Academy announcements
- Quick actions: pay now, report absence, contact academy - Quick actions: pay now, report absence, contact academy
### 3. Schedule & Calendar ### 3. Schedule & Calendar
...@@ -55,7 +380,7 @@ A visually stunning, data-driven mobile app for guardians and participants. Push ...@@ -55,7 +380,7 @@ A visually stunning, data-driven mobile app for guardians and participants. Push
- Documents (uploaded/pending) - Documents (uploaded/pending)
### 7. Notifications (THE CORE FEATURE) ### 7. Notifications (THE CORE FEATURE)
- Push notifications via FCM (Firebase Cloud Messaging) - Push notifications via FCM
- Works when app is closed (background/terminated) - Works when app is closed (background/terminated)
- Types: - Types:
- Session reminder (30 min before) - Session reminder (30 min before)
...@@ -79,7 +404,7 @@ A visually stunning, data-driven mobile app for guardians and participants. Push ...@@ -79,7 +404,7 @@ A visually stunning, data-driven mobile app for guardians and participants. Push
- Feedback after session (optional, academy-configurable) - Feedback after session (optional, academy-configurable)
### 9. Explore Academy ### 9. Explore Academy
- Academy about/info (pulled from website_settings) - Academy about/info (pulled from API)
- News feed (from website news module) - News feed (from website news module)
- Photo/video gallery - Photo/video gallery
- Branches & locations (with map) - Branches & locations (with map)
...@@ -89,7 +414,7 @@ A visually stunning, data-driven mobile app for guardians and participants. Push ...@@ -89,7 +414,7 @@ A visually stunning, data-driven mobile app for guardians and participants. Push
- Programs offered (browse available programs) - Programs offered (browse available programs)
### 10. Events ### 10. Events
- Upcoming events list (from website events module) - Upcoming events list
- Event details + registration (join from app) - Event details + registration (join from app)
- Event gallery (photos/videos after event) - Event gallery (photos/videos after event)
- Push notification when new event is published - Push notification when new event is published
...@@ -101,7 +426,7 @@ A visually stunning, data-driven mobile app for guardians and participants. Push ...@@ -101,7 +426,7 @@ A visually stunning, data-driven mobile app for guardians and participants. Push
- Installment plans visible + selectable - Installment plans visible + selectable
- Order status tracking (pending delivery → delivered) - Order status tracking (pending delivery → delivered)
- Purchase history - Purchase history
- System warns if product already purchased this year (same logic as POS) - System warns if product already purchased this year
### 12. Kit / Uniform Ordering ### 12. Kit / Uniform Ordering
- If academy sells kits with specific sizes - If academy sells kits with specific sizes
...@@ -112,223 +437,224 @@ A visually stunning, data-driven mobile app for guardians and participants. Push ...@@ -112,223 +437,224 @@ A visually stunning, data-driven mobile app for guardians and participants. Push
## System-Side Preparations (Laravel Backend) ## System-Side Preparations (Laravel Backend)
### A. API Layer — NEW: `routes/api.php` ### A. API Layer — `routes/api.php`
We need a full REST API. Currently the system is Livewire-only (server-rendered). The mobile app needs JSON endpoints. All endpoints versioned under `/api/v1/`.
**App config (public, no auth):**
- `GET /api/v1/app/config` — returns academy branding, feature flags, min version
**Auth endpoints:** **Auth endpoints:**
- `POST /api/auth/otp/request` — send OTP to phone - `POST /api/v1/auth/otp/request` — send OTP to phone
- `POST /api/auth/otp/verify` — verify OTP, return Sanctum token - `POST /api/v1/auth/otp/verify` — verify OTP, return Sanctum token
- `POST /api/auth/logout` — revoke token - `POST /api/v1/auth/logout` — revoke token
- `GET /api/auth/me` — current user + linked participants - `GET /api/v1/auth/me` — current user + linked participants
**Data endpoints:** **Data endpoints:**
- `GET /api/participants/{uuid}` — profile - `GET /api/v1/participants/{uuid}` — profile
- `GET /api/participants/{uuid}/schedule` — upcoming sessions - `GET /api/v1/participants/{uuid}/summary` — dashboard data (schedule + balance + attendance)
- `GET /api/participants/{uuid}/attendance` — history + stats - `GET /api/v1/participants/{uuid}/schedule` — upcoming sessions
- `GET /api/participants/{uuid}/invoices` — outstanding + paid - `GET /api/v1/participants/{uuid}/attendance` — history + stats
- `GET /api/participants/{uuid}/enrollments` — active programs - `GET /api/v1/participants/{uuid}/invoices` — outstanding + paid
- `GET /api/participants/{uuid}/documents` — uploaded docs - `GET /api/v1/participants/{uuid}/enrollments` — active programs
- `GET /api/guardian/children` — all linked participants - `GET /api/v1/participants/{uuid}/documents` — uploaded docs
- `GET /api/v1/guardian/children` — all linked participants
**Action endpoints:** **Action endpoints:**
- `POST /api/payments/initiate` — start online payment - `POST /api/v1/payments/initiate` — start online payment
- `POST /api/payments/callback` — payment gateway webhook - `POST /api/v1/payments/callback` — payment gateway webhook
- `POST /api/absences/report` — report planned absence - `POST /api/v1/absences/report` — report planned absence
- `POST /api/messages/send` — contact academy - `POST /api/v1/messages/send` — contact academy
- `POST /api/notifications/preferences` — update notification prefs - `POST /api/v1/notifications/preferences` — update notification prefs
- `PATCH /api/notifications/{id}/read` — mark as read - `PATCH /api/v1/notifications/{id}/read` — mark as read
- `GET /api/notifications` — paginated notification history - `GET /api/v1/notifications` — paginated notification history
**Device endpoints:**
- `POST /api/v1/devices/register` — register FCM token
- `PATCH /api/v1/devices/refresh` — update token on refresh
- `DELETE /api/v1/devices/{token}` — unregister on logout
**Shop endpoints:** **Shop endpoints:**
- `GET /api/participants/{uuid}/products` — available essential products (with already-purchased flag) - `GET /api/v1/products` — available essential products (with `already_purchased_at` field)
- `POST /api/orders/create` — purchase product from app - `POST /api/v1/orders/create` — purchase product from app
- `GET /api/participants/{uuid}/orders` — order history - `GET /api/v1/orders` — order history
- `GET /api/orders/{uuid}` — order status/details - `GET /api/v1/orders/{uuid}` — order status/details
**Academy explore endpoints:** **Academy explore endpoints:**
- `GET /api/academies/{slug}/news` — news feed - `GET /api/v1/academy/news` — news feed (paginated)
- `GET /api/academies/{slug}/gallery` — photo/video gallery - `GET /api/v1/academy/gallery` — photo/video gallery
- `GET /api/academies/{slug}/programs` — available programs - `GET /api/v1/academy/programs` — available programs
- `GET /api/v1/academy/events` — events list
**Public endpoints (no auth):** - `POST /api/v1/events/{uuid}/register` — join event
- `GET /api/academies/{slug}/info` — academy branding for login screen
- `GET /api/academies/{slug}/events` — public events ### B. Database Changes
- `POST /api/events/{uuid}/register` — join event (auth required)
```sql
### B. Push Notification Infrastructure -- device_tokens table
CREATE TABLE device_tokens (
**Database changes:** id bigserial PRIMARY KEY,
``` academy_id bigint NOT NULL REFERENCES academies(id),
// New migration: device_tokens table user_id bigint NOT NULL REFERENCES users(id),
- id device_token varchar(255) NOT NULL,
- user_id (FK) platform varchar(10) NOT NULL CHECK (platform IN ('android', 'ios')),
- device_token (string, unique) — FCM token device_name varchar(100),
- platform (enum: ios, android) app_version varchar(20),
- device_name (nullable) is_active boolean DEFAULT true,
- is_active (bool, default true) last_used_at timestamp,
- last_used_at (timestamp) created_at timestamp,
- created_at, updated_at updated_at timestamp,
UNIQUE(device_token)
// New migration: notification_logs additions );
- Add column: push_sent_at (nullable timestamp) CREATE INDEX idx_device_tokens_user ON device_tokens(user_id, is_active);
- Add column: push_status (enum: pending, sent, failed, delivered)
- Add column: fcm_message_id (nullable string) -- system_settings additions (per academy)
``` -- key: 'firebase_service_account_json' → value: full JSON content
-- key: 'auth_otp_mode' → value: 'demo' or 'sms'
**New service: `PushNotificationService`** -- key: 'sms_provider' → value: 'log' | 'smsmisr' | 'victorylink' | 'twilio'
- Register device token -- key: 'app_min_version' → value: '1.0.0'
- Send push via FCM HTTP v1 API -- key: 'app_maintenance_mode' → value: 'false'
- Handle token refresh (old token → new token) -- key: 'app_features_shop' → value: 'true'
- Handle unregistered tokens (remove from DB) -- key: 'app_features_events' → value: 'true'
- Batch sending (multiple recipients) -- key: 'app_features_chat' → value: 'false'
- Priority: high (payment/attendance) vs normal (announcements)
**FCM Integration:**
- Firebase project setup (one per academy OR one shared project)
- Server key stored in `system_settings` or `.env`
- Package: `kreait/firebase-php` (official Firebase Admin SDK for PHP)
**Event → Push mapping (extend existing listeners):**
``` ```
AttendanceMarked → push to guardian ("تم تسجيل حضور [اسم] اليوم")
InvoiceCreated → push to guardian ("فاتورة جديدة بقيمة X ج.م")
PaymentConfirmed → push to payer ("تم تأكيد الدفع")
SessionCancelled → push to group participants' guardians
SessionReminder → scheduled job, 30 min before each session
InstallmentDue → scheduled job, 1 day before due_date
MedicalCertExpiring → scheduled job, 7 days before expiry
Announcement → push to all active participants' guardians
```
### C. Authentication — Sanctum API Tokens
**What exists:** Session-based auth (web only)
**What's needed:** Token-based auth for mobile
```php ### C. App Config Endpoint Response
// config/sanctum.php — already installed with Laravel
// Add to User model: ```json
use Laravel\Sanctum\HasApiTokens; // GET /api/v1/app/config (public, no auth needed)
{
// Token abilities for mobile: "academy": {
'mobile:read' read own data "name": "OC Sport",
'mobile:pay' initiate payments "name_ar": "او سي سبورت",
'mobile:notify' manage notification preferences "logo_url": "https://oc-sport.caprover.al-arcade.com/storage/logos/logo.png",
"cover_url": "https://...",
"primary_color": "#1B5E20",
"accent_color": "#4CAF50",
"phone": "+201234567890",
"whatsapp": "+201234567890"
},
"features": {
"shop": true,
"events": true,
"chat": false,
"online_payment": true,
"gallery": true
},
"auth": {
"mode": "demo",
"demo_otp": "123456"
},
"app": {
"min_version": "1.0.0",
"maintenance": false,
"maintenance_message": null
}
}
``` ```
**OTP Flow:** ### D. New System Settings UI Section
1. User enters phone → system finds User by phone
2. Generate 6-digit OTP, store in cache (5 min TTL)
3. Send via SMS gateway (existing SMS interface)
4. User enters OTP → verify → issue Sanctum token
5. Token stored on device, sent as `Bearer` header
### D. Online Payment Gateway
**Options for Egypt:**
- Paymob (most common, supports cards + wallets)
- Fawry (kiosk + online)
- Kashier
**Integration points:** Add a "Mobile App" section in system settings:
- `POST /api/payments/initiate` → create Paymob intention → return payment URL/iframe key - Toggle: OTP mode (demo/sms)
- Webhook: `POST /api/payments/callback` → verify HMAC → confirm payment → update invoice - Upload: Firebase service account JSON
- Mobile app opens payment URL in WebView or uses Paymob SDK - Toggle: Shop enabled
- Toggle: Events enabled
- Toggle: Chat enabled
- Input: Minimum app version
- Toggle: Maintenance mode
- Input: Maintenance message
### E. Scheduled Jobs (new or modified) ---
```
// New jobs for mobile push
schedule:run
├── SendSessionReminders — every minute, find sessions starting in 30 min
├── SendInstallmentReminders — daily 9am, find installments due tomorrow
├── SendMedicalCertWarnings — daily, find certs expiring in 7 days
└── CleanExpiredDeviceTokens — weekly, remove tokens unused for 90 days
```
### F. API Resource Classes (response formatting)
```
app/Http/Resources/
├── ParticipantResource.php
├── SessionResource.php
├── AttendanceResource.php
├── InvoiceResource.php
├── PaymentResource.php
├── EnrollmentResource.php
├── NotificationResource.php
├── AcademyInfoResource.php
└── EventResource.php
```
### G. Rate Limiting & Security
- Rate limit OTP: 3 attempts per phone per 10 min ## New Client Deployment Checklist
- Rate limit API: 60 requests/min per token
- Token expiry: 30 days (refresh on each use) ### Firebase Setup (5 min)
- Device token validation (only accept valid FCM tokens) - [ ] Create Firebase project at console.firebase.google.com
- API versioning: `/api/v1/...` - [ ] Add Android app → package: `com.alcaptain.{clientslug}`
- [ ] Download `google-services.json`
- [ ] (If iOS) Add iOS app → bundle ID same → download plist
- [ ] Project Settings → Service Accounts → Generate new private key
- [ ] Save the private key JSON
### Flutter Build (5 min)
- [ ] Edit `lib/config/instance.dart`:
- `baseUrl` = client's instance URL
- `appName` / `appNameAr` = client's name
- `packageId` = `com.alcaptain.{clientslug}`
- [ ] Replace `android/app/google-services.json`
- [ ] (iOS) Replace `ios/Runner/GoogleService-Info.plist`
- [ ] Update `android/app/build.gradle``applicationId` matches packageId
- [ ] `flutter build appbundle --release`
### Laravel Instance Config (2 min)
- [ ] System Settings → Mobile App → upload Firebase service account JSON
- [ ] Set OTP mode to 'demo' for testing, 'sms' for production
- [ ] Enable/disable features (shop, events, chat)
### Play Store Upload
- [ ] Client's Google Play Console → Create new app
- [ ] Upload AAB
- [ ] Store listing: use academy's name, logo, screenshots
- [ ] Submit for review
--- ---
## Build Order (System Side) ## Build Order (System Side)
### Phase 1: API Foundation ### Phase 1: API Foundation + App Config
1. Install/configure Sanctum for API tokens 1. `routes/api.php` with `/api/v1/` prefix
2. Create `routes/api.php` with versioned prefix 2. `GET /api/v1/app/config` endpoint (public)
3. OTP auth flow (request → verify → token) 3. Sanctum token auth setup
4. Guardian/participant read endpoints 4. OTP auth flow with demo mode
5. API Resources for response formatting 5. Device token registration endpoint
6. Guardian/participant read endpoints
7. API Resource classes
### Phase 2: Push Notifications ### Phase 2: Push Notifications
6. `device_tokens` migration 8. `device_tokens` migration
7. `PushNotificationService` (FCM HTTP v1) 9. `composer require kreait/firebase-php`
8. Device token registration endpoint 10. `PushNotificationService` (FCM HTTP v1)
9. Extend existing event listeners to trigger push 11. System Settings UI for Firebase JSON upload
10. Session reminder scheduled job 12. Extend event listeners → push triggers
13. Session reminder scheduled job
### Phase 3: Financial API 14. Installment due reminder job
11. Invoice list/detail endpoints
12. Payment gateway integration (Paymob) ### Phase 3: Explore + Shop
13. Payment initiation + webhook 15. Academy info/news/gallery API endpoints
14. Installment tracking endpoints 16. Events API + registration
17. Products API (with `already_purchased_at` check)
### Phase 4: Actions & Communication 18. Order creation + payment endpoint
15. Report absence endpoint
16. Notification preferences endpoint ### Phase 4: Financial API
17. Message/contact academy endpoint 19. Invoice list/detail endpoints
18. Notification history endpoint 20. Payment gateway integration (Paymob)
21. Payment initiation + webhook callback
### Phase 5: Polish 22. Installment tracking endpoints
19. Rate limiting middleware
20. API documentation (auto-generated) ### Phase 5: Communication + Polish
21. Error response standardization (Arabic messages) 23. Report absence endpoint
22. Push notification preferences (per-type toggle) 24. Notification preferences endpoint
25. Contact/message endpoint
26. Rate limiting middleware
27. Error response standardization (Arabic messages)
--- ---
## What The Flutter App Needs From Us ## Decisions Made
| Feature | Endpoint | Push Event | | Decision | Choice | Reason |
|---------|----------|------------| |----------|--------|--------|
| Login | `POST /api/auth/otp/*` | — | | App per client | Separate build per client | Clients paid for their own app |
| Home dashboard | `GET /api/participants/{id}/summary` | — | | Config approach | Single `instance.dart` file | One URL change = new client app |
| Schedule | `GET /api/participants/{id}/schedule` | session_cancelled, session_reminder | | Firebase | One project per client | Isolation, client ownership |
| Attendance | `GET /api/participants/{id}/attendance` | attendance_marked | | Auth without SMS | Demo mode (OTP = 123456) | Allows prototyping immediately |
| Invoices | `GET /api/participants/{id}/invoices` | invoice_created | | Branding | Fetched from API at runtime | No need to rebuild for logo/color changes |
| Pay | `POST /api/payments/initiate` | payment_confirmed | | API versioning | `/api/v1/` prefix | Future-proof without breaking old apps |
| Notifications | `GET /api/notifications` | (all types) |
| Profile | `GET /api/participants/{id}` | — |
| Events | `GET /api/academies/{slug}/events` | event_announcement |
---
## Decisions Needed ## Decisions Still Needed
1. **Payment gateway** — Paymob? Fawry? Both? 1. **Payment gateway** — Paymob? Fawry? Both?
2. **FCM project** — One shared Firebase project or per-academy? 2. **SMS provider** — SMS Misr? Victory Link? Twilio?
3. **SMS provider for OTP** — Use existing SMS gateway or add Twilio/Vonage? 3. **iOS** — Are we building for iOS too or Android-only initially?
4. **App branding** — One generic app branded per academy at runtime? Or separate builds? 4. **Offline mode** — How much data cached locally? How stale is acceptable?
5. **Offline mode** — Cache schedule/attendance locally? How stale is acceptable? 5. **Language** — Arabic only? Or bilingual toggle in app?
6. **Language** — Arabic only? Or bilingual like the web?
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment