Ahmed Ali

Platform

Genoa Tours

Moved tourist-entry approvals for 115 partner agencies off email and spreadsheets, in English and Arabic.

Client
Genoa Tours
Year
2026
Scope
Two front-ends, one API
Role
Proposed → built → delivered → hosted
Genoa · Partner Portalreplica

The problem

Travel agencies in Algeria and Morocco file tourist manifests for entry into Egypt. Before this, that ran on email and spreadsheets.

Genoa Tours sits between North-African travel agencies and Egyptian entry approvals. An agency sends a manifest — flight details plus passport data for every tourist on it. Genoa's staff review it, send it back for corrections, approve it, and issue a formal Arabic guarantee letter that the tourists travel on.

The whole loop ran over email. No status an agency could check without asking, no structured record of what had been submitted, every letter assembled by hand. With 115 partner companies that does not scale, and the failure mode is a tourist at an airport holding a wrong document.

What I built

  1. The screen

    • Partner portal for 115 agencies
    • Internal admin console
    • English and Arabic, full RTL
  2. The logic

    • ASP.NET Core 10 API
    • JWT with silent refresh, four roles
    • Arabic letters rendered on the server
  3. The data

    • PostgreSQL through EF Core
    • Idempotent migration scripts
    • Documents on a persistent path

Three pieces, all mine end to end. The partner portal is what agencies see: submit a manifest, track it through review → revision → approval, download the guarantee letter when it clears. The point was to remove the phone call.

The admin console is the internal surface — review and approve, manage partner companies, set per-tourist pricing, track running balances and payments, edit the document template, generate letters.

Both run on one ASP.NET Core 10 API over PostgreSQL with EF Core. Auth is JWT with silent refresh across four roles, so a partner can never see another partner's manifests.

Both front-ends are English and Arabic with full RTL: the layout mirrors rather than having a dictionary bolted on. Arabic is the language the letters are issued in, so this was load-bearing.

I host it myself on a Linux VPS: Caddy terminating TLS, systemd supervising, PostgreSQL on the same box, and a self-contained .NET publish so the server needs no runtime installed. Schema changes ship as idempotent EF migration scripts. Uploads and generated PDFs live on a persistent path outside the deploy directory, so a redeploy never takes client documents with it.

One hard decision

Render the letters on the server

The guarantee letter is the document tourists travel on, so it has to come out the same whoever clicks the button and whatever browser they use. I render it on the server from one template Genoa's staff can edit. Getting Arabic to shape and render correctly in a headless Linux container, with the right fonts present, the right direction and no missing glyphs, took longer than the feature it supports. It also lets one letter fold several flights together instead of one per flight.

What I'd change now

I'd write the end-to-end tests in both languages from the first screen. Mirroring the layout for Arabic touched every page, and checking both directions by hand is the slowest part of a release.

Stack

  • Next.js 16
  • React 19
  • TypeScript
  • Tailwind v4
  • ASP.NET Core 10
  • PostgreSQL
  • EF Core
  • JWT auth
  • Caddy
  • systemd

Both surfaces are authenticated — partners are provisioned by Genoa, not self-signup — so there's no public URL. The partner portal is rebuilt in the console with invented records: /desk/genoa-partner.

Next case studyLConnectMarketplace · 2025