Back to Portfolio
Case Study · Freelance Cloud Project

AVIS Ossolana
Member Portal

A production member management portal for an Italian blood donor association — replacing a legacy CSV workflow with a full-stack SPA + REST API, containerized and deployed on a VPS. Live at portale.avisossolana.it, serving 4,000+ real members.

🟢 Live in Production 👥 4,000+ Members 👤 Solo Developer ☁️ Cloud · VPS ⚛️ React · TypeScript · .NET 8 🐳 Docker + Nginx

Problem & Solution

AVIS Ossolana ODV (Associazione Volontari Italiani Sangue, Domodossola) needed to replace a manual CSV-based workflow on a legacy server with a modern, accessible portal. The new system needed to work for non-technical staff managing 4,000+ blood donors, with mobile access and integrated communication tools — and it had to be trustworthy enough to hold real members' personal and health-adjacent data.

Problem
  • Member data in CSV files on a legacy Linux server — no UI
  • No automated alerts for donors with upcoming donation dates
  • Manual WhatsApp messages sent one-by-one to thousands of members
  • No self-service for members to check their own donation history
  • No visibility into award (benemerenza) status per member
Solution
  • Modern SPA portal accessible from any device, including mobile
  • Dashboard with upcoming donation alerts (≤30 days) at a glance
  • Bulk WhatsApp sending with template system and guided 5-step wizard
  • Member self-service login: personal stats, donation history, awards
  • Full admin CRUD + CSV export + communication log history
  • Legacy CSV data migrated in — 4,000+ member records and donation history

Interface Walkthrough

Screenshots from a local instance seeded with fabricated demo data — the interface is identical to production, but no real member is shown here.

Authentication & Onboarding 2 screens
Dashboards 2 screens
Member Management 3 screens
WhatsApp Communications 4 screens

Full-Stack Architecture & Deployment

The system uses a decoupled frontend/backend architecture served through a single Nginx entry point. Everything runs containerized on a cloud VPS, with SSL handled at the Nginx layer.

☁️ Cloud VPS — Linux
Any device, any browser — HTTPS only
👥
Users
Browser / Mobile
Single entry point · SSL termination · Gzip · SPA routing
🔀
Nginx
Reverse Proxy · SSL
Docker Compose Network
Static Assets
SPA: React Router handles client-side navigation without full-page reloads
⚛️
React SPA
TypeScript · Vite · Tailwind
/api/* Proxy
Minimal APIs: lightweight REST endpoints, no controller overhead for an MVP
⚙️
.NET 8 API
Minimal APIs · JWT Auth
PostgreSQL: open-source, GDPR-friendly, excellent EF Core support
🗄️
PostgreSQL
EF Core
External Integrations
Meta Cloud API for WhatsApp Business — live integration for templated member messaging
💬
WhatsApp API
Meta Cloud API
Let's Encrypt for free SSL — automated renewal via certbot
🔒
Let's Encrypt
Auto SSL · certbot

Hover over each node to see the design rationale

Why Each Technology Was Chosen

Every choice was made considering the client context: non-technical admin staff, mobile access needs, Italian data sovereignty (GDPR), and budget constraints of a volunteer association.

⚛️
React + TypeScript Chosen

The admin WhatsApp wizard has 5 sequential steps with complex state — React's component model handles this far better than server-rendered pages. TypeScript adds compile-time safety when dealing with member data structures.

Vite Chosen

Near-instant HMR during development and optimized production builds. Simpler configuration than webpack for a project of this size. Native ESM support aligns with the modern TypeScript setup.

🎨
Tailwind CSS Chosen

Rapid prototyping with consistent design tokens. The custom AVIS red (#C8102E) is easily configured as a Tailwind color. Utility-first approach avoids naming CSS classes for dozens of small UI components.

⚙️
.NET 8 Minimal APIs Chosen

Minimal APIs keep the backend lean — all route handlers in Program.cs, no controller boilerplate for an MVP with ~15 endpoints. Familiarity with the .NET ecosystem accelerated development. Chose over Node.js/Express for strong typing and future EF Core integration.

🗄️
PostgreSQL Chosen

Chosen over SQL Server to avoid licensing costs for a non-profit client. Open-source, well-supported by EF Core, and excellent for GDPR-sensitive member data. The legacy CSV export was migrated in through EF Core migrations.

🔀
Nginx + Docker Chosen

Single-domain deployment: Nginx serves the React build as static files and proxies /api/* to the .NET container. No CORS complexity. Docker Compose ensures identical dev/prod environments and easy server migration.

🇮🇹
Italian Cloud VPS Chosen

Italian hosting provider = data residency within Italy, simplifying GDPR compliance for member personal data. Cost-effective for a volunteer association budget. Italian-language support for the client.

💬
Meta Cloud API Chosen

Official WhatsApp Business API for bulk messaging. Chosen over Twilio for lower per-message cost at scale and direct Meta integration. The template system was built to match the Meta template variable format, and now includes webhook signature verification hardened during the security review.

Application Features

Three role tiers with separate views and permissions — member, admin, and superadmin — plus a registration & approval workflow for new members and a GDPR-compliant privacy policy page.

Admin / Superadmin Role
  • Member registration review and admin approval workflow
  • Member CRUD with 7-type advanced filtering (name, award, WhatsApp status, upcoming date…)
  • Multi-select + bulk operations (CSV export, batch WhatsApp send)
  • WhatsApp template CRUD with {{variable}} syntax, plus a parallel email template system
  • 5-step guided sending wizard: recipients → template → preview → confirm → results
  • Communication log & audit trail with delivery status per recipient
  • Dashboard: active members, total donations, upcoming alerts, missing WhatsApp count
Member (Socio) Role
  • Self-registration, pending admin approval before activation
  • Login with tessera number (donor card ID), JWT-authenticated
  • First-login mandatory password change wizard
  • Personal dashboard: donation count, blood group, upcoming date, award status
  • Award visualization: Bronze → Silver → Gold → Gold Superior
  • Settings: password change, account security info

Problems Solved

CHALLENGE 01

WhatsApp Template Variable Preview in Real-Time

Problem

Admin staff needed to see exactly what each WhatsApp message would look like for each recipient before sending. With 100+ members and dynamic variables like {{nome}}, {{prossima_donazione}}, previewing each one individually would be unusable.

Solution

Template variables are substituted client-side in React using string replacement with the selected member's actual data. The preview step in the wizard renders the final resolved message for each recipient before the admin confirms. No backend round-trip needed for preview.

CHALLENGE 02

Single-Domain Deployment: SPA + API Behind One Nginx

Problem

Running a React SPA and a .NET API on the same domain requires careful routing. React Router handles client-side navigation (all routes should return index.html). The API needs its own path prefix. Getting SSL, gzip, and SPA fallback all working together in one Nginx config was non-trivial.

Solution

Nginx serves the React build as static files with a try_files $uri /index.html fallback for SPA routing. Any request to /api/* is proxied to the .NET container. SSL is terminated at Nginx. Static assets have 1-year cache headers. All in a single nginx.conf.

CHALLENGE 03

Migrating 4,000+ Real Members Off a Legacy CSV

Problem

The existing member database was a CSV export from a legacy Linux server with inconsistent formatting. Column names in Italian, dates in Italian format (DD/MM/YYYY), and missing fields on older records. A direct import would have produced dirty data for thousands of real members and their donation history.

Solution

A migration script handled the transformation: Italian date parsing, column mapping to the PostgreSQL schema, default value injection for missing fields, and duplicate tessera (card number) detection before the EF Core seed migration ran. All 4,000+ member records and their donation history were imported cleanly ahead of go-live.

CHALLENGE 04

Security Review Before Going Live with Real Member Data

Problem

Once the schema held real names, dates of birth, and donation/health-adjacent records for 4,000+ people, the MVP-era shortcuts couldn't ship: an XSS-exposed input, insecure fallback values for JWT/app secrets, database ports reachable outside the Docker network, and no signature verification on the incoming WhatsApp webhook.

Solution

A dedicated security review and hardening pass ran before production launch: sanitized/escaped the vulnerable input, removed insecure secret fallbacks in favor of required environment configuration, closed the exposed database ports at the Docker Compose network level, and added Meta webhook signature verification. Deployed only after the pass was clean.

Production Deployment on a Cloud VPS

The entire stack is containerized. Deploying to a new server is a single docker compose up -d --build command. SSL is handled outside the containers via certbot on the host.

services:
  backend: # .NET 8 Minimal API
    build: ./backend # multi-stage: SDK → Runtime
    expose: 5000 # internal only, not public
    healthcheck: curl /swagger every 30s
    environment: ASPNETCORE_ENVIRONMENT=Production

  frontend: # Node → Nginx Alpine
    build: ./frontend # npm ci → vite build → nginx
    ports: 80:80
    depends_on: backend
    # Nginx: SPA routing + /api proxy + gzip + 1yr cache

# After deploy on VPS:
$ certbot --nginx -d portale.avisossolana.it
# SSL auto-renewal via systemd timer — live in production

Current Status & Impact

Live in production at portale.avisossolana.it, replacing the legacy CSV workflow for the client's full membership. Marco owned the project end-to-end: architecture, development, security hardening, deployment, data migration, and ongoing production support.

👥
4,000+
Real members
🎭
3
User roles
📋
5-step
WhatsApp wizard
🐳
2
Docker services
☁️
Live
Production VPS
🔒
GDPR
Compliant

Interested in working together?

Got a cloud project, an internal tool, or a web platform that needs building? Let's talk.