Skip to content

aashir-athar/BludStack-RN

Repository files navigation

BludStack

The fastest way to find a blood donor.

A production-grade, real-time blood donation network. Recipients post requests in seconds. Compatible donors nearby are notified instantly. Lives are saved on the same map, in the same minute.


Expo SDK React Native TypeScript Supabase Node Vercel


Architecture New Architecture Reanimated FlashList Status License


Live API · Issues · Discussions · Roadmap


Why BludStack exists

Every two seconds, someone somewhere needs blood. In emergencies, the bottleneck isn't supply — it's the time it takes to find a compatible donor nearby. Hospitals call relatives. Relatives forward WhatsApp messages. Messages spread to people who can't help. By the time a compatible donor sees the request, the window is closed.

BludStack flips that. The moment a recipient pins their hospital, our backend expands outward in geo-fenced rings — 1 km → 5 km → 15 km → 30 km → 50 km → country-wide — pushing real-time alerts only to compatible, eligible, available donors at each stage. When a donor accepts, the recipient sees their live GPS heartbeat on a map. Like Uber, but for the most important ride of someone's life.


Table of contents

  1. Highlights
  2. Architecture
  3. Tech stack
  4. Project structure
  5. The N-donors rule
  6. Geo-fence escalation algorithm
  7. Blood compatibility matrix
  8. Design system
  9. Performance budget
  10. Security model
  11. Backend setup
  12. Mobile setup
  13. Environment variables
  14. Deployment
  15. Cron jobs
  16. Push notifications
  17. API reference
  18. Database schema
  19. Roadmap
  20. FAQ
  21. Contributing
  22. License
  23. Author

Highlights

  • Real-time push escalation — compatible donors in widening geo-rings are paged in priority order until someone accepts. No SMS fan-out, no group chats, no time wasted on incompatibles.
  • Atomic accept + complete RPCsaccept_blood_request and complete_blood_donation are SECURITY DEFINER PostgreSQL functions that enforce capacity, cooldown, age, and the N-donors rule in a single transaction. No race conditions, no double-accepts, no half-fulfilled state.
  • Live donor heartbeat — once a donor accepts, foreground GPS pushes their location to /donations/heartbeat. The recipient sees the donor moving on a map in real time, Uber-driver style.
  • N units = N donors — a 5-unit request needs 5 distinct donors to accept and complete. One donor per unit. No counterfeit fulfilment.
  • Row-level security everywhere — every table has RLS policies. Donors only see what they're allowed to see. Recipient phone numbers are never exposed until a donor commits.
  • Killed-state notifications — push notifications wake the app from any state on Android and iOS via expo-notifications with per-OEM tuning (see PUSH_NOTIFICATIONS.md).
  • 120 FPS target on low-end Android — Reanimated v4 worklets, FlashList v2, memoized cells, expo-image with cache. Tested on Realme/Xiaomi mid-tier devices.
  • Tri-state theme — system / dark / light. Crimson on warm onyx (dark) or warm bone (light). All colors flow through theme tokens. Zero hardcoded hex outside Colors.ts.
  • Skeleton loading only — every loading state is a shimmer placeholder shaped like the content. No spinners, no ActivityIndicator, no jarring pop-ins.
  • No emojis anywhere — Ionicons + custom SVG (BrandMark) for every visual symbol. Period.
  • No external error monitoringerrorReporter is a typed logger wrapper. No Sentry, no Bugsnag, no Crashlytics. Crash logs stay on-device or in your own server logs.
  • AsyncStorage everywhere — never react-native-mmkv. Sensitive tokens go through expo-secure-store. Auth, query cache, KV all live in @react-native-async-storage/async-storage.

Architecture

flowchart LR
  subgraph Mobile["React Native / Expo SDK 54"]
    UI["Screens · Reanimated v4 · FlashList v2"]
    AuthCtx["AuthContext · ThemeContext · ToastContext"]
    APIClient["utils/api.ts"]
    SBClient["@supabase/supabase-js"]
    Notif["expo-notifications"]
  end

  subgraph Vercel["Vercel Serverless (Express)"]
    API["/auth · /profiles · /requests · /donations · /chat · /admin"]
    Cron["Vercel Cron · escalation · expiry · cleanup"]
  end

  subgraph Supabase["Supabase"]
    PG[("Postgres · RLS · RPCs")]
    RT["Realtime · postgres_changes"]
    Auth["Auth · OTP email"]
  end

  subgraph Push["Push"]
    Expo["Expo Push API"]
    APNs["APNs"]
    FCM["FCM"]
  end

  UI --> AuthCtx --> APIClient
  UI --> SBClient
  APIClient -- "HTTPS · Bearer token" --> API
  SBClient -- "Realtime WS" --> RT
  SBClient -- "Auth" --> Auth
  API -- "service_role" --> PG
  Cron -- "/internal/cron" --> API
  API -- "send push" --> Expo
  Expo --> APNs
  Expo --> FCM
  Expo -- "delivered" --> Notif
  Notif --> UI
Loading

Single-source-of-truth contracts

  • supabase_schema.sql — the only schema file. Tables, enums, RLS, RPCs, grants, realtime publication.
  • AuthContext — the only place the app reads profile from. Updates merge the server-returned row (the backend may normalise — e.g., role downgrade on age fail — so the server response is canonical).
  • ThemeContext — tri-state (system/dark/light) with Appearance.addChangeListener subscription + dark default fallback (Expo Go Android limitation workaround).
  • utils/api.ts — every HTTP call. Backend errors surface as typed ApiError discriminated by isApiError.

Tech stack

Layer Choice Why
Mobile framework Expo SDK 54 + RN 0.81.5 + React 19.1 New Architecture default, first-class Reanimated v4, FlashList v2, expo-glass-effect.
Language TypeScript (strict) Catches every shape mismatch at compile time.
Navigation Expo Router v6 File-based, typed routes, group-aware guards via <Stack.Protected> semantics.
State React Context (Auth, Theme, Toast) Three contexts for an app this size; deliberately not Zustand (see REDESIGN_PLAN.md §13).
Server cache Direct fetch + Supabase Realtime Realtime is the cache-invalidation mechanism. TanStack Query intentionally not added.
Animations Reanimated v4 worklets UI-thread 120 FPS target. Spring/timing/sequence/repeat — every interactive animation.
Lists @shopify/flash-list v2 maintainVisibleContentPosition.startRenderingFromBottom for chat; recycler for feeds.
Storage @react-native-async-storage/async-storage + expo-secure-store Never MMKV. Sensitive tokens go through SecureStore.
Images expo-image with cache policy Disk + memory cache, modern formats, faster than RN's Image.
Maps react-native-maps Default provider with theme switching.
Backend Node 20 + Express on Vercel Serverless Stateless functions, free tier-friendly, sub-100 ms cold starts in regions.
Database Supabase Postgres with RLS Realtime subscriptions on postgres_changes. Atomic RPCs for accept/complete.
Auth Supabase Auth · email OTP No passwords. 6-digit code per session.
Notifications expo-notifications + Expo Push API Killed-state delivery; per-OEM channels for Android (see PUSH_NOTIFICATIONS.md).
Geo Haversine + deltaFromKm + reverse-geocode via expo-location All radius math in-app; reverse geocode hits the OS.

Project structure

BludStack/
├── mobile/                     # Expo SDK 54 app
│   ├── app/
│   │   ├── _layout.tsx         # ErrorBoundary > GH > KeyboardProvider > SafeArea > Theme > Auth > Toast > RootNavigator
│   │   ├── index.tsx           # Cold-start gate (<Redirect>)
│   │   ├── (auth)/             # OTP sign-in (sheet UI + accent strip + handle)
│   │   ├── onboarding.tsx      # Multi-step profile setup (sheet pattern)
│   │   ├── (tabs)/
│   │   │   ├── index.tsx           # Donor home — FlashList, memoized renderItem
│   │   │   ├── request.tsx         # Recipient post-request — Uber DECIDE/VERIFY/ACT
│   │   │   ├── donors.tsx          # Discovery — list + map toggle, filter chips
│   │   │   ├── my-requests.tsx     # Recipient's own requests
│   │   │   ├── history.tsx         # Donor donation timeline
│   │   │   └── profile.tsx         # Profile + settings
│   │   ├── request/[id].tsx    # Request detail — live donor heartbeat
│   │   ├── donor/[id].tsx      # Donor detail — compatibility + contact pills
│   │   ├── map/live.tsx        # Live tracking map
│   │   ├── chat.tsx            # Donor ↔ recipient chat (FlashList v2 inverted)
│   │   └── profile/edit.tsx    # Edit profile modal
│   ├── components/             # Theme-tokenised primitives
│   ├── contexts/               # Auth · Theme · Toast
│   ├── hooks/                  # Requests · location · notifications · heartbeat · chat
│   ├── utils/                  # api.ts · supabase.ts · geo.ts · helpers.ts
│   ├── constants/              # Colors · Typography · BloodData
│   └── lib/errorReporter.ts    # Typed logger wrapper — NEVER Sentry
├── backend/                    # Express on Vercel
│   ├── src/
│   │   ├── controllers/        # auth · profile · request · donation · chat · stats · admin
│   │   ├── middleware/         # auth · errorHandler · validator
│   │   ├── routes/             # Express routers per resource
│   │   ├── utils/supabaseAdmin.js   # service_role client
│   │   └── server.js
│   └── vercel.json             # Function + cron config
├── supabase_schema.sql         # Single source of truth — tables, RLS, RPCs, grants
├── migrations/                 # Delta files for non-destructive Studio runs
├── verify_schema.sql           # Single-row PASS/FAIL diagnostic
├── PUSH_NOTIFICATIONS.md       # Killed-state delivery playbook
├── REDESIGN_PLAN.md            # Canonical design language + 100%-wired mandate
└── HARDENING_NOTES.md          # 28-flaw fix log

The N-donors rule

A request for N units of blood needs N distinct donors to accept and complete. One donor per unit. No exceptions.

flowchart TD
  A["Recipient posts: 5 units AB+"] --> B["Geo-fence escalation begins"]
  B --> C{"5 donors accepted?"}
  C -- No --> D["Push to next ring"]
  D --> B
  C -- Yes --> E["Request stays 'active' until ALL 5 complete"]
  E --> F["Donor 1..5 mark 'arrived & donated'"]
  F --> G["complete_blood_donation RPC<br/>increments total_donations<br/>+ sets last_donation_date<br/>+ checks fulfillment"]
  G --> H{"All N donors completed?"}
  H -- No --> E
  H -- Yes --> I["Request → 'fulfilled'<br/>Recipient + donors notified"]
Loading

The complete_blood_donation RPC enforces this atomically. A 5th donor cannot "complete" what the 1st through 4th haven't already donated against. There is no path to mark a request fulfilled while units remain.


Geo-fence escalation algorithm

flowchart LR
  Start(["Request posted"]) --> R1["Ring 1 km · ~30 s"]
  R1 -- "no accept" --> R2["Ring 5 km · ~60 s"]
  R2 -- "no accept" --> R3["Ring 15 km · ~90 s"]
  R3 -- "no accept" --> R4["Ring 30 km · ~2 min"]
  R4 -- "no accept" --> R5["Ring 50 km · ~3 min"]
  R5 -- "no accept" --> Country["Country-wide"]
  R1 -- "accept" --> Heart["Live heartbeat starts"]
  R2 -- "accept" --> Heart
  R3 -- "accept" --> Heart
  R4 -- "accept" --> Heart
  R5 -- "accept" --> Heart
  Country -- "accept" --> Heart
Loading

At each ring, the backend:

  1. Calls nearby_compatible_donors(req_id, radius_km) — a Postgres function that filters by compatibility matrix, eligibility (age, cooldown, availability), and ST_DWithin on the location.
  2. Sends Expo Push to the cohort.
  3. Records ring + cohort size in request_escalations for the analytics + cron timing decisions.
  4. Holds the ring for the configured TTL or until N donors accept.

The geo-fence claim is DB-persisted with compare-and-set (CAS) semantics — two cron invocations or two cohorts cannot double-page the same ring.


Blood compatibility matrix

Recipient Can receive from
O− O−
O+ O−, O+
A− O−, A−
A+ O−, O+, A−, A+
B− O−, B−
B+ O−, O+, B−, B+
AB− O−, A−, B−, AB−
AB+ All groups (universal recipient)

DONOR_FOR_RECIPIENT in constants/BloodData.ts is the canonical mapping. The backend filters by this matrix server-side; the client filters the home feed defensively.


Design system

The (tabs)/request.tsx screen is the locked design reference. Every other screen mirrors its pattern. The full spec lives in REDESIGN_PLAN.md §14, but the headline rules are:

  • Sheet language — bottom sheet ascends with -Spacing[5] overlap; brand accent strip (3 px crimson) + handle on top; spring entrance from translateY 60.
  • Section labels — uppercase, FontWeight.black, LetterSpacing.widest, theme.textMuted, indented Spacing[2]. Questions, not nouns.
  • Inline CTA at form end — summary row (colored dot + bold tier label + bullet-separated facts) + pill button + footer micro-copy (privacy/reassurance).
  • Breathing animation on critical/destructive CTAs (1.00 ↔ 1.02, 1500 ms infinite). Loss aversion via motion.
  • Haptics on every PressableHaptics.selectionAsync() for toggles, Haptics.impactAsync(Medium) for commits.
  • Theme tokens onlytheme.primary, theme.success, theme.warning, theme.danger, theme.surface, theme.cardElevated, theme.background, theme.border, theme.borderStrong, theme.textPrimary, theme.textMuted, theme.textTertiary, theme.textOnPrimary. Never hardcoded hex.
  • Pill-shaped 2026 visualRadius.pill for actions and chips, Radius.xl for cards, Radius['2xl'] for sheet tops.
  • Skeleton loading onlySkeleton shimmer matching content geometry. Never ActivityIndicator.

Performance budget

Metric Target How we hit it
Frame rate on mid-tier Android 120 FPS Reanimated v4 worklets on UI thread; every animation uses useSharedValue + useAnimatedStyle.
Cold start < 2 s to first paint Splash screen until AuthContext resolves; app/index.tsx redirect gate prevents wrong-default-screen flash.
List scroll 0 dropped frames FlashList v2 with getItemType, memoized renderItem, stable keyExtractor.
Map 60 FPS pan/zoom react-native-maps with provider={PROVIDER_DEFAULT}, marker count capped per ring.
Realtime reconnect < 5 s Supabase client default retry; channels named uniquely with uniqueChannelName(prefix) to avoid collision on rapid re-mount.
Image cache hit > 90% expo-image with cachePolicy="memory-disk".

Security model

  • Row-level security on every table. Service role is the only path to bypass — and it's only used inside SECURITY DEFINER RPCs and the backend supabaseAdmin client.
  • Trigger guards on profiles allow the service role to update server-managed fields (total_donations, last_donation_date, is_verified, push_token) while blocking client-side writes. The trigger checks current_user, current_role, and the JWT's request.jwt.claim.role OR'd together — robust across PostgREST configurations.
  • Allowed-fields filter in profileController.js — clients can only PATCH a whitelist. Server-managed fields are stripped server-side regardless of what the client sends.
  • Atomic RPCs for accept and complete — capacity, cooldown, age, and idempotency are checked inside the transaction. Failures roll back cleanly.
  • JWT bearer auth on every API endpoint. The backend reads Authorization: Bearer <token> and verifies against Supabase Auth before any work.
  • No service-role key in the client. The mobile app only knows the anon key. Service-role lives in backend/.env and Vercel env vars.
  • OTP rate-limiting via Supabase Auth defaults. No client-side bypass.
  • No PII in logs. errorReporter redacts email/phone/coordinates by default.

Backend setup

# Clone
git clone https://github.com/aashir-athar/BludStack.git
cd BludStack/backend

# Install
npm install

# Configure
cp .env.example .env
# Fill in: SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, JWT_SECRET, EXPO_ACCESS_TOKEN

# Run the schema (single source of truth)
psql $SUPABASE_DB_URL < ../supabase_schema.sql

# (Optional) Verify the schema is fully applied
psql $SUPABASE_DB_URL < ../verify_schema.sql

# Dev
npm run dev

# Production (Vercel)
vercel --prod

Mobile setup

cd BludStack/mobile

# Install
npm install

# Configure
cp .env.example .env
# Fill in: EXPO_PUBLIC_API_URL, EXPO_PUBLIC_SUPABASE_URL, EXPO_PUBLIC_SUPABASE_ANON_KEY

# Dev (Expo Go for quick iteration; dev build for push notifications)
npx expo start

# Dev build (required for push, recommended for everything else)
npx eas build --profile development --platform android
npx eas build --profile development --platform ios

# Production build
npx eas build --profile production --platform all

Heads-up: push notifications will not be delivered to Expo Go on Android since SDK 53. Build a development client to test pushes.


Environment variables

Backend (backend/.env)

Key Where to find it
SUPABASE_URL Supabase Project Settings → API
SUPABASE_SERVICE_ROLE_KEY Supabase Project Settings → API (server-side only)
SUPABASE_DB_URL Project Settings → Database → Connection string (used for psql)
JWT_SECRET Same value as Supabase JWT secret
EXPO_ACCESS_TOKEN Expo dashboard → Access Tokens (for push)
INTERNAL_CRON_TOKEN Random secret — used by Vercel Cron to call /internal/*
DEBUG_ERRORS 1 to surface pg codes in error responses; 0 in prod

Mobile (mobile/.env)

Key Where to find it
EXPO_PUBLIC_API_URL Your Vercel deployment URL (e.g. https://bludstack.vercel.app)
EXPO_PUBLIC_SUPABASE_URL Supabase Project Settings → API
EXPO_PUBLIC_SUPABASE_ANON_KEY Supabase Project Settings → API (anon, not service role)

Deployment

The backend ships to Vercel Serverless. vercel.json declares the function timeout, regional preferences, and the cron schedule. Push to main → Vercel builds and promotes automatically.

The mobile app ships via EAS Build + EAS Submit. eas.json defines profiles for development, preview, and production. Internal channel updates flow via EAS Update (OTA) for non-native changes.


Cron jobs

Configured in vercel.json:

Path Schedule Purpose
/internal/cron/escalate * * * * * (every minute) Promotes active requests to the next geo-fence ring if their TTL has elapsed.
/internal/cron/expire */5 * * * * Flips overdue requests to expired; notifies recipients.
/internal/cron/cleanup 0 3 * * * (daily 03:00 UTC) Purges stale request_responses in pending status older than 7 days.

Each cron handler authenticates with INTERNAL_CRON_TOKEN before doing work.


Push notifications

Killed-state delivery is the most important reliability axis. The full playbook is in PUSH_NOTIFICATIONS.md, including per-OEM tuning (Xiaomi, OnePlus, Realme, Samsung battery saver / auto-launch / lock-screen permissions).

Critical-channel highlights:

  • Android — dedicated critical channel with bypass-DnD, alarm sound, max importance. The app registers it at startup.
  • iOScritical-alert entitlement requested at first run.
  • Tap deep-linkinguseNotificationDeepLinks reads Notifications.useLastNotificationResponse() in _layout.tsx and routes to the right screen even from a cold launch.

API reference

Base URL: https://<your-vercel-url> (or http://localhost:3000 in dev). Every endpoint requires Authorization: Bearer <supabase access token> unless noted.

Method Path Body / Query Returns
POST /profiles/register RegisterPayload { profile, downgraded? }
PATCH /profiles/me Partial<ProfilePatch> UserProfile
GET /profiles/nearby-donors ?lat&lon&radiusKm&bloodGroup Donor[]
POST /requests CreateRequestPayload BloodRequest
GET /requests/nearby ?lat&lon BloodRequest[] (excludes own)
GET /requests/mine BloodRequest[]
POST /requests/:id/cancel BloodRequest
POST /donations/accept { requestId } RequestResponse
POST /donations/decline { requestId } RequestResponse
POST /donations/complete { requestId, donorId } BloodRequest (status → fulfilled when all units complete)
POST /donations/heartbeat { requestId, lat, lon } 204
GET /stats/me { donations, livesHelped, lastDonatedAt }
GET /chat/:requestId/messages ?before&limit ChatMessage[]
POST /chat/:requestId/messages { clientId, receiverId, body } ChatMessage

Errors surface as ApiError with code, message, optional pgCode/pgHint/details (when DEBUG_ERRORS=1).


Database schema

The canonical schema is supabase_schema.sql at the repo root. Top-level tables:

Table Purpose
profiles One row per user; role, blood group, contact, eligibility metadata
blood_requests Recipient-posted needs; status, urgency, units, lat/lon, hospital
request_responses Donor responses to a request; status, optional donor_lat/lon/donor_location_updated_at
request_escalations Geo-fence ring + cohort log per request
chat_messages Donor ↔ recipient thread scoped to request_id; idempotent on client_id
push_tokens Per-device Expo push tokens

All tables have RLS policies. The full DDL is generated and version-controlled — drop the file into Supabase Studio's SQL Editor to reset a project.


Roadmap

  • Group requests (multiple recipients on a single thread for ward-level needs)
  • In-app video consult for critical cases (E2EE via the noble crypto stack)
  • Donor badges + leaderboards (city-anonymised)
  • Web companion (Next.js) for hospitals to post on behalf of patients
  • Multilingual UI (Urdu, Hindi, Arabic, Spanish — full RTL where applicable)
  • Donor-recipient chat with images + voice notes (expo-audio, not expo-av)
  • Apple Vision Pro spatial map view for blood banks (experimental)

FAQ

Is my data safe? Yes. Every table has row-level security. Recipient phone numbers are never visible to anyone until a donor accepts. No PII in logs. No external error monitoring SDKs.

What if no donor accepts? The geo-fence keeps expanding to country-wide. The request stays active until you cancel it or all units are fulfilled. You can repost at any time.

Can I be both a donor and a recipient? Yes — pick "Both" at onboarding. The role gates which tabs you see; "Both" sees everything.

How is donor eligibility enforced? Age (≥ 18) is server-enforced. The 90-day cooldown is enforced by the accept_blood_request RPC. Availability is a profile toggle.

Why no MMKV? Project decision. AsyncStorage covers every use case at our scale, with broader compatibility and zero native-link friction.

Why no Sentry? Project decision. errorReporter keeps logs on-device or routes them to your own server. No third-party data export.

Why no emojis? Brand decision. We use Ionicons + custom SVG for every symbol. Consistency across themes, locales, and assistive tech is more important than playful glyphs.

Why is Reanimated v4 the only animation library? UI-thread 120 FPS target. RN's legacy Animated runs on JS thread and drops frames under load. Reanimated worklets compile to UI-thread code.

Can I run BludStack against a different backend? The mobile app only talks to two surfaces: Supabase (RT + Auth + DB) and the Express backend. Swap EXPO_PUBLIC_API_URL and EXPO_PUBLIC_SUPABASE_URL and you're good.

Where do I file bugs / request features? GitHub Issues for bugs, Discussions for features and questions.


Contributing

Pull requests are welcome. The bar is high — but the door is open.

Ground rules

  1. One branch per change. Branch names are kebab-case with a prefix: feat/, fix/, chore/, docs/, refactor/. Date suffix optional.
  2. No emojis in code, copy, or commit messages.
  3. No Alert.alert for errors — use the useToast context.
  4. No ActivityIndicator — use Skeleton shaped like the content.
  5. No hardcoded hex outside Colors.ts. Everything flows through theme.* tokens.
  6. No react-native-mmkv. All persistence is AsyncStorage or expo-secure-store.
  7. No external error monitoring SDKs.
  8. Strict TypeScript — no any in new code. Use the SDK's generated types.
  9. Memoize list cells. React.memo, stable keyExtractor, getItemType for FlashList.
  10. Haptic on every interactive Pressable. Selection for toggles, impact for commits.
  11. Skeleton loading only. Never spinners.
  12. npx expo install <pkg> — never edit package.json versions by hand.

Local development

# Backend
cd backend && npm run dev   # nodemon + tsx if added

# Mobile (Expo Go for fast iteration)
cd mobile && npx expo start

# Mobile (dev build — required for push notifications)
cd mobile && npx eas build --profile development --platform android

Pull-request flow

  1. Fork → branch → push.
  2. Open PR against main. Title format: feat(scope): short description (Conventional Commits).
  3. The PR must:
    • Pass tsc --noEmit.
    • Pass expo-doctor.
    • Have a screenshot or short video for any UI change.
    • Have a Test plan section describing what you exercised.
  4. Reviewers will check against the Canonical Design Language.

Sponsorship & contact

If BludStack helps someone you love, consider sponsoring on GitHub or sharing the app with a hospital near you.


License

MIT License © 2026 Aashir Athar

You are free to use, modify, and distribute this software with attribution. The clinical workflow (atomic accept/complete, age + cooldown enforcement, RLS posture) is intentionally permissive so other lives can be saved with it.


Author

Aashir Athar Founder · Engineer · Designer

GitHub · LinkedIn · Sponsor


Built in Lahore. For everyone, anywhere.

About

BludStack is a full-stack blood donation platform that connects people in need of blood with nearby eligible donors in real time. When a blood request is posted, the system automatically expands outward in geo-fenced rings — notifying donors 1 km away first, then 5 km, 15 km, 30 km, 50 km, and finally country-wide — until a donor accepts.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors