A healthcare ecosystem where patients, doctors, and administrators finally run on the same source of truth.
Tabibi replaces phone-call scheduling, paper prescriptions, and disconnected clinic admin with three interconnected applications — a Patient app, a Doctor app, and an Admin web portal — built on one shared Flutter design system and one Firebase backbone. Every appointment, chat, and prescription moves through a role-guarded, security-hardened pipeline: a race-condition-proof booking engine, algorithmically isolated chat channels, and a single-use cryptographic doctor invitation system that make cross-role data leakage structurally impossible, not just policy-discouraged.
Six failure modes of clinic operations that Tabibi was built to close, one by one.
Three apps, one Firebase backbone, and a security model enforced at the database — not the screen.
3.1 — Three-platform interconnection
The Patient app (appointment_doctor), Doctor app (doctor_app_project), and Admin web portal (appointment_doctor_admin) never talk to each other directly — they read and write the same shared Firestore collections, and every write from one app becomes an instant, real-time read for the others. A doctor approving a booking on their phone updates the same appointments document a patient is watching on theirs, and the same row an admin is auditing from the web console.
3.2 — Security foundation
Every guard below is enforced in at least two independent layers, so a compromised client can never bypass it on its own.
Role enforcement at sign-in
smartLogin validates the authenticated user's role field the moment credentials are checked — before any home screen renders. A wrong role produces an immediate sign-out and a localized error, never a silent fallback.
Email verification guard
The AuthWrapper checks user.emailVerified before granting access to any authenticated screen. An unverified account is routed to a pending-verification state and cannot reach the patient directory, the chat, or the booking flow.
Router guard — AuthWrapper
AuthWrapper continuously listens to the Firebase Auth state stream. If an admin blocks a patient or suspends a doctor mid-session, the guard evicts that session in real time — no re-login required to enforce the change.
Chat algorithmic isolation
createOrGetChat only opens or resumes a thread when matchesPatient && matchesDoctor both evaluate true — a strict logical conjunction that makes a misrouted conversation structurally unreachable, not merely unlikely.
Firestore security rules
Access to /chats/{chatId} is restricted server-side, independent of any client code:
3.3 — Shared UI design system
Rather than re-implement buttons, cards, and colors three times, every design decision was extracted once into an internal Flutter package, tabibi_ui, imported by all three applications. This is what makes a TabibiButton in the Doctor app pixel-identical to the one a patient taps, with zero duplicated styling code to drift out of sync.
The patient's entire relationship with their care — search, book, message, and remember — lives in one place.
1. Auth, role isolation & email verification guard
Registration collects name, email, phone, gender, and password, then a Firebase confirmation email is sent immediately. AuthCubit.signInPatient enforces expectedRole: 'patient', so even a valid doctor account entered into this login screen is rejected before it reaches a home screen.
smartLogin checks the role field before proceeding.The login screen is intentionally unstyled toward any single role — the same shape is reused across apps, and it's the backend check, not the UI, that decides who gets through. A patient typing correct credentials for a doctor-role account is bounced here with a clear, localized message rather than a generic error.
Immediately after submission, Firebase Auth issues a verification email and the account is provisioned in the users collection with emailVerified: false. The account exists, but AuthWrapper will hold it at the door until that flag flips.
Password recovery sits one tap away from login. It's deliberately minimal — a single email field and a "Send Link" action — keeping account recovery fast for a patient who may be trying to reach a doctor about something urgent.
2. Doctor discovery & specialty filter
The home screen queries Firestore scoped to role == 'doctor' && status == 'active', so a doctor who hasn't cleared admin approval never appears in a patient's search results in the first place.
Manar's home screen shows an Awaiting Approval appointment with Dr. منار صلاح in Dermatology, alongside a specialty grid pulled live from the specialties collection — the same collection an admin manages from the web portal. Add a specialty on the web, and it appears in this grid instantly, with zero app redeploy.
A doctor card carries exactly what a patient needs to decide: specialty, bio, working hours, and fee, with a direct Book Appointment Now action and a shortcut into the direct chat — both of which respect the same role and matching guards described in Section 3.2.
3. Smart appointment booking engine & anti-abuse guards
- Daily booking quota limit guard — once a doctor's configured maximum daily bookings is reached, the entire day locks and disappears from the selector, preventing overbooking abuse.
- Consultation vs. re-examination non-collision guard — an algorithm keeps
Initial Consultationslots andRe-examinationslots from ever overlapping on the same calendar. - Real-time slot exclusion & race condition guard — the instant one patient's booking is written, that slot is pulled from every other active booking session, closing the double-booking window entirely.
The scenario below is the full three-step flow, screen by screen: pick a date, pick a time, add context, confirm.
The progress bar at the top of the teal header commits to a three-step contract with the patient before they've entered any data — reducing the anxiety of an open-ended form.
This grid is the live output of the anti-abuse engine: it dynamically filters out any slot already carrying a pending or confirmed appointment, so what the patient sees is guaranteed bookable at render time.
Confirming here writes a new document to appointments with initial status pending and immediately broadcasts the real-time slot exclusion — every other patient's Step 2 screen for that doctor updates within the same session.
This success state closes the loop the moment the write completes — no waiting for a push notification to know the request went through, even though one is dispatched to the doctor immediately after.
View Medical Examination once completed, or Cancel Appointment while pending).The same appointment document changes shape as it moves through its lifecycle — a Pending card offers cancellation, a Completed one swaps that action for a direct link into the clinical record, without the patient ever leaving this screen.
4. Direct consultation chat & advanced messaging
- Real-time 1-on-1 chat restricted to a booked patient/doctor pair.
- Delete for me / delete for everyone message removal with live UI sync.
- In-place message editing with an
(edited)label. - Star / favorite critical medical instructions for rapid retrieval later.
- Automatic + manual archiving — chats lock to read-only once the appointment completes.
- Doctor-side blocking/muting as a safeguard against abusive patients.
- Media attachments — lab reports, scans, PDFs, and voice notes.
- Read receipts and unread badge counters.
Archive, Starred Messages, and Delete — above a pinned message and the live message thread with a teal outbound bubble and neutral inbound ones.This single screen demonstrates three of the eight sub-features at once: the pin at the top surfaces a message the patient chose to keep visible, the overflow menu exposes archiving and starring without cluttering the primary composer, and the composer itself keeps a voice-note affordance one tap away from the text field.
5. Patient profile & app settings
- Language switcher — Arabic (RTL) to English (LTR) at runtime, no restart.
- Dark / light mode persisted via
SharedPreferences. - Profile editing — name, phone, blood type, avatar.
- Issue reporting straight into the Admin Support Hub.
My Data (Edit Profile, Change Password) and General (App Settings, Support Center), with Logout isolated at the bottom.Grouping account edits separately from general app behavior keeps a patient from accidentally wandering into a destructive action (Logout) while trying to reach something routine like a language toggle.
New Ticket / My Tickets tab pair.Submitting here writes directly to the support_tickets collection, where it appears — with the same subject and priority — inside the admin's Support Ticket Hub in real time, closing Problem #6 from Section 2.
6. Medical records & prescription hub
Every completed consultation leaves behind a structured clinical record the patient can revisit indefinitely — diagnosis, vitals, medications, and doctor's notes, plus a downloadable prescription PDF.
View Prescription action.This is the exact record a doctor authored moments after the visit (see Section 5, Feature 4) — rendered here read-only for the patient, with a direct path into the print-ready PDF a pharmacy will accept.
7. Push notification engine
- Appointment acceptance alert — instant FCM push on doctor approval.
- Rejection/cancellation alert with the stated reason.
- Re-examination booking alert, auto-triggered by the doctor scheduling a follow-up.
- Platform broadcast announcements pushed down from Admin.
New until opened.A Cloud Function listens for status mutations on the appointments collection and dispatches a targeted FCM payload to the patient's fcmToken the moment a doctor taps Approve — the notification and the in-app bookings list update within the same second.
Practice management, scheduling, and prescriptions — run entirely from a phone.
1. Dual-path doctor onboarding system
A clinic can bring doctors in two different ways, and the app supports both from the same registration screen.
| Path A — Invitation Code | Path B — Direct Registration |
|---|---|
| Doctor enters an admin-issued 6-digit code | Doctor registers with no code, uploading proof of medical license |
System validates isUsed == false | Account enters a pending state on submission |
Account is activated immediately (active) | Admin reviews the uploaded credential and approves or rejects |
Code is marked isUsed: true — one-time use only | Doctor receives a push notification the moment a decision is made |
| Doctor proceeds straight to schedule setup | Only an approved account can configure a schedule or appear in patient search |
medical_specialty dropdown, and a Professional Proof upload dropzone.The Professional Proof uploader is mandatory for Path B — this file becomes the proofDocumentUrl an admin will inspect before flipping the account to active.
Invitation Code field under "Clinic Access Code," with the reminder banner "Registration is restricted to doctors only."Entering a valid code here shortcuts the entire review process — validateInvitationCode checks the code against invitation_codes, and a match sets status: 'active' and isUsed: true in the same atomic write, so the code can never be handed off and reused by someone else.
This is the twin of the patient login screen from Section 4, but wired to expectedRole: 'doctor' — a patient's valid credentials entered here are rejected the same way a doctor's are rejected on the patient side.
Consistency here isn't accidental: it's the shared tabibi_ui package's form components rendering the same way regardless of which app imports them.
2. Clinic schedule & availability configuration
Saving this form persists directly to doctors/{uid}, and a dynamic slot generator recalculates every collision-free 30-minute block across the selected working days — instantly reflected in the patient app's Choose Time screen shown earlier.
3. Appointment & visit history management
View Patient History shortcut.Approving, cancelling, or completing an appointment here updates the status field in the shared appointments collection and triggers the same real-time push notification pipeline the patient side depends on.
Medical Examination or View Prescription.A doctor opening a returning patient's history sees the exact same clinical entries the patient can see from their own Medical Records Hub — the same document, two authorized viewers.
4. Tabibi prescription engine (PDF & canvas)
Visits & Prescriptions list with inline PDF and edit icons per entry, and an add_prescription action at the bottom.Each visit row already carries its diagnosis, weight, blood pressure, and notes inline — a doctor can scan a patient's whole history without opening a single sub-page.
revisit_date picker, an Add Medication list-builder, and a Before Images attachment slot.Saving this form is what PrescriptionPdfBuilder renders into a multi-page, print-ready vector PDF, while PrescriptionImageRenderer produces a high-resolution PNG/JPG alternative sized for sharing over WhatsApp — the same clinic-branded header appears on both outputs. Setting revisit_date here is also what auto-triggers the re-examination push alert on the patient's device.
5. Doctor profile & app settings
This dashboard is a doctor-scoped mirror of the admin's platform-wide dashboard in Section 6 — same visual language, same real-time counters, scoped to one practice instead of the whole platform.
Fee, bio, and clinic address edits made here update the same doctors/{uid} document the patient-facing profile card in Section 4 reads from — there's no separate "public profile" to keep in sync.
The theme-color picker is a lightweight personalization layer — it lets an individual doctor choose an accent that suits their own taste without touching the platform's actual design tokens (covered in Section 7), which stay fixed regardless of this setting.
This is the doctor-side entry point into the same encrypted, algorithmically-isolated messaging system detailed in Section 4, Feature 4.
New.Every incoming booking request lands here the instant a patient confirms Step 3 of the booking flow — a doctor never has to refresh the Appointments tab to know a new request has arrived.
support_tickets collection.Whether a bug report comes from a patient or a doctor, it surfaces in the exact same Admin Support Ticket Hub queue, tagged by role, shown in Section 6.
Everything a platform operator needs to see and control, in one Flutter Web console.
1. Main dashboard analytics & overview
These four numbers are real-time aggregations over Firestore snapshots on users, appointments, doctors, and invitation_codes — an admin refreshing this page mid-morning sees the exact booking volume as it happens, not a nightly batch report.
2. Modern responsive web header & navigation
Visible in every screenshot in this section: a fixed dark sidebar (Dashboard, Pending Join Requests, Doctors, Patients, Appointments, Specialization, Invitation Codes, Support Tickets, Announcements, Settings) paired with a light top bar carrying a dark/light toggle and an Arabic/English switcher (عربي) — so an Arabic-speaking admin can flip the entire console's direction without losing their place.
3. Comprehensive doctor management & moderation
The four icons under every doctor card map directly to the moderation controls the brief calls for: inspecting that doctor's appointment load, editing their specialty/fee/phone/address, opening a credential-verification dialog for their uploaded license, and — as a last resort — permanently deleting the account. An Approve / Reject / Suspend decision and a CSV export of the approved roster both live one click deeper from this same list.
4. Patient management & moderation
Flagging a fraudulent account here sets isActive: false on that patient's record — and because AuthWrapper is watching the auth state stream on the mobile side, the affected device is signed out within the same session, not on its next cold start.
5. Appointments monitoring & status management
The row for Aug 10, 2026 shows a rejected status next to an inspect icon — clicking through surfaces the cancellation reason an admin or doctor logged, giving full audit visibility into why a specific slot didn't go ahead.
6. Dynamic specialties management (CRUD)
Approved Specialty.Typing "Orthopedic Surgery" and confirming here writes to the specialties collection with an automated Arabic/English translation lookup and an internal _normalizeArabic text normalizer — the new specialty appears in the Doctor app's registration dropdown and the Patient app's discovery grid at the same instant.
7. Single-use invitation code generator
Available for use with a creation date and a one-click copy icon.Code 077179 was generated on 2026-08-31 — the same day this dashboard was captured — by buildInvitationCode, a 6-digit cryptographic random generator. The moment a doctor consumes it during registration, its badge flips from Available for use to Used, with the consuming doctor's name and timestamp recorded, and the code is permanently locked against replay.
8. Announcements & broadcast system
Every announcement carries a title, body, type (info / warning / success), and optional expiry date; once created, it renders as a banner inside both the Patient and Doctor mobile apps without either app needing a fresh deploy.
9. Support ticket hub
IN_PROGRESS, already carrying an Admin Reply) alongside three patient-submitted tickets still OPEN.Each card is tagged by submitter role (DOCTOR / PATIENT) so an admin can triage clinical-side complaints separately from patient-side ones, reply inline, and move the status through Open → In Progress → Closed as the issue is resolved.
One visual language, expressed identically across a phone screen and a browser tab.
7.1 — The Tabibi healthcare design philosophy
The platform's primary colour, Emerald Teal #0D9488, was chosen deliberately for its association with medical trust, cleanliness, and calm — visible in every header gradient, primary button, and status accent across all three apps. Full dark and light mode support runs throughout, which matters most for the Doctor app during long night shifts, and the overall visual language favours clean surgical spacing, high-contrast readability, soft glass-like card surfaces, Iconsax's medical-appropriate iconography, and understated micro-interactions over decorative noise.
7.2 — Shared design token system
All three platforms import the same tabibi_ui tokens rather than hard-coding values locally:
7.3 — Component reuse across platforms
A TabibiCard, TabibiButton, TabibiStatusBadge, and UserAvatar render with identical corner radii, shadow depth, and type scale whether they're compiled into the Patient app, the Doctor app, or Flutter Web — compare the status-badge pill on the Patient app's Completed booking card against the admin's Completed appointment badge: same component, same styling, two different runtimes.
One layer sits outside this shared system by design: the Doctor app's Settings screen offers an individual Theme Color picker (Emerald Teal, Royal Blue, Medical Purple) so a single doctor can personalize their own accent colour without altering the tabibi_ui tokens every other user relies on — a preference, not a platform-wide redesign.
Seven engineering decisions that turn "should be secure" into "structurally can't fail."
1. Firestore security rules — server-side enforcement
Chat access is gated at the database, not the app: /chats/{chatId} only permits reads and writes from isChatParticipant(resource.data) || isAdmin(). A modified or jailbroken client gains nothing, because the rule lives outside any code the client controls.
2. Role guard at the authentication layer
smartLogin checks the role field before any token is issued for app access — a doctor's credentials simply cannot open the patient app, and the reverse holds symmetrically.
3. Mandatory email verification guard
AuthWrapper checks user.emailVerified before routing to any authenticated screen, closing the patient-database pollution risk described in Section 2.
4. Chat algorithmic isolation
createOrGetChat's conjunctive guard — matchesPatient && matchesDoctor — prevents any possibility of chat misrouting between unrelated accounts.
5. Anti-abuse booking engine (three layers)
Daily quota lock closes an entire day once its booking maximum is hit; a consultation-vs-re-examination algorithm keeps the two appointment types from ever colliding; and real-time slot exclusion removes a booked slot from every other active session the instant it's claimed.
6. Single-use invitation code enforcement
Each 6-digit code is consumed exactly once via an atomic Firestore write — isUsed: true plus usedByUid — permanently blocking reuse even if the code leaks after being consumed.
7. FCM push notification architecture
Cloud Functions listen for mutations on the appointments collection and dispatch targeted FCM payloads to the recipient's fcmToken the moment a status changes — the notification pipeline is entirely event-driven, with no polling on either client.
What building Tabibi actually proved.
Clinical impact
Tabibi replaces the phone-call-and-walk-in scheduling model with a self-service booking engine patients can use at any hour, removes paper prescriptions from the loop entirely in favour of a permanent digital record, and turns re-examination follow-ups from a manual phone chase into an automatic push alert the moment a doctor schedules one.
Engineering achievement
The final system is a production-grade, three-application ecosystem sharing a single design package, with zero cross-role data contamination enforced at the database layer, real-time synchronization across every screen shown in this document, and security hardening applied consistently at authentication, chat, booking, and invitation-code layers rather than bolted on after the fact.
Design achievement
Every screenshot in Sections 4 through 6 comes from the same tabibi_ui token set, delivering consistent, accessible healthcare UX across mobile and web, with full bilingual Arabic (RTL) / English (LTR) support and dark/light mode available from day one — not retrofitted later.
Developer reflection
The decisions that mattered most in hindsight were the ones invisible to any single screenshot: designing the role guard before a single UI screen existed, working out the booking-collision algorithm on paper before writing the Firestore query, extracting the shared UI package early enough that no app ever had to "catch up" to it, and treating every Firestore security rule as the real access-control layer — with the client-side guard only ever a convenience on top.