A web-based WiFi billing and hotspot management platform built for Nigerian ISPs and hotspot operators. Customers can purchase internet packages or redeem prepaid vouchers via Paystack; admins manage packages, vouchers, payments, advertisements, analytics, and MikroTik router integration from a dashboard.
- Overview
- Architecture
- Prerequisites
- Project Structure
- Local Development Setup
- Supabase Backend Setup
- Paystack Configuration
- MikroTik Relay Setup
- Production Deployment
- Environment Variables Reference
- Admin Credentials
- Troubleshooting
- Hosting on Android Termux (Old Phone)
NetConnect bridges a MikroTik hotspot with cloud payment processing. It is designed for LAN-only operation where the customer portal and a small relay server run inside the same network as the MikroTik router, while Supabase and Paystack remain in the cloud.
- Public portal: package selection, Paystack checkout, voucher redemption, ad banners, support page
- Admin dashboard: dashboard stats, package management, voucher generation/reports, payments log, user sessions, ads management, system settings
- Supabase Edge Functions for payment, voucher redemption, session expiry, MikroTik activation, and admin credentials
- MikroTik REST API relay for live voucher sync and hotspot user creation
- 5-minute session expiry cron job
Customer Device
│
▼
MikroTik Hotspot (192.168.88.1)
│
├── Portal (React SPA) ─────── http://<local_ip>:8080
│
└── MikroTik Relay (Node.js) ─ http://<local_ip>:3000
│
▼
Internet
│
├── Supabase (Auth + PostgreSQL + Edge Functions + Storage)
│
└── Paystack (payment gateway)
- Frontend: React 18 + Vite + TypeScript + Tailwind CSS + shadcn/ui
- Backend: Supabase Edge Functions (Deno runtime)
- Database: PostgreSQL managed by Supabase
- Relay: Node.js + Express (runs on your LAN, inside
tasks/mikrotik-relay/) - Payments: Paystack (Nigerian card/bank transfers)
- Supabase project (free tier works)
- Paystack business account + secret/public keys
- MikroTik router with Hotspot enabled (L009 / RB750 / hAP series)
- Node.js ≥ 20
- pnpm ≥ 9 or npm ≥ 10
- Supabase CLI (for local Edge Function testing and deployment)
- Git (optional)
Verify tools:
node -v # v20.x or higher
pnpm -v # 9.x or higher
supabase --version.
├── index.html # Vite entry HTML
├── package.json # Frontend dependencies and scripts
├── postcss.config.js # Tailwind/PostCSS config
├── tailwind.config.js # Tailwind theme config
├── tsconfig*.json # TypeScript configs
├── vite.config.ts # Vite configuration
├── public/ # Static assets (logos, favicon, error illustrations)
├── src/
│ ├── App.tsx # Root application component
│ ├── main.tsx # React mount point
│ ├── routes.tsx # React Router route definitions
│ ├── index.css # Global styles
│ ├── components/ # Shared UI components (shadcn/ui)
│ ├── components/layouts/ # PublicLayout, AdminLayout
│ ├── components/portal/ # Portal-only components (AdBanner)
│ ├── contexts/ # React contexts (AuthContext)
│ ├── db/ # Supabase client setup
│ ├── hooks/ # Custom React hooks
│ ├── lib/ # Utility helpers and API callers
│ ├── pages/ # Public and admin pages
│ └── types/ # TypeScript type definitions
├── supabase/
│ ├── config.toml # Supabase CLI config
│ ├── functions/ # Edge Functions (Deno)
│ │ ├── paystack-payment/ # Initialize Paystack checkout
│ │ ├── paystack-webhook/ # Handle Paystack webhooks
│ │ ├── generate-vouchers/ # Admin bulk voucher generation
│ │ ├── redeem-voucher/ # Customer voucher redemption
│ │ ├── retry-activation/ # Retry MikroTik user activation
│ │ ├── expire-sessions/ # Expire old hotspot sessions (cron)
│ │ └── update-admin-credentials/ # Admin password update
│ ├── migrations/ # Database schema migrations
│ └── secrets/required.json # Required secret keys list
├── tasks/
│ └── mikrotik-relay/ # LAN relay server (Node.js + Express)
│ ├── index.js
│ ├── package.json
│ ├── .env.example
│ └── README.md
└── README.md # This file
cd /workspace/app-czpf690oj3ep # or wherever you extracted the ZIPpnpm install
# or
npm installCreate .env in the project root:
VITE_SUPABASE_URL=https://<your-project>.supabase.co
VITE_SUPABASE_ANON_KEY=<your-anon-key>
VITE_PAYSTACK_PUBLIC_KEY=pk_test_...
VITE_PAYSTACK_SECRET_KEY=sk_test_...The frontend needs
VITE_PAYSTACK_SECRET_KEYonly when running the demo/test flow; in production, the Edge Function uses the server-side secret stored in Supabase Secrets.
pnpm dev -- --host 0.0.0.0
# or
npm run dev -- --host 0.0.0.0The portal will be available at http://localhost:5173.
- Go to Supabase Dashboard
- Create a new project and save the Project URL and Anon Key
- Navigate to Project Settings → API → save
service_rolekey too (only for local CLI/admin use)
supabase login
supabase link --project-ref <your-project-ref>supabase db pushThis applies all SQL files inside supabase/migrations/, creating the schema, default admin user, RLS policies, and cron jobs.
In the Supabase Dashboard → Edge Functions → Secrets (or via CLI):
supabase secrets set PAYSTACK_SECRET_KEY=sk_test_...
supabase secrets set PAYSTACK_PUBLIC_KEY=pk_test_...
supabase secrets set SUPABASE_URL=https://<your-project>.supabase.co
supabase secrets set SUPABASE_SERVICE_ROLE_KEY=<service-role-key>Optional / MikroTik-specific secrets:
supabase secrets set MIKROTIK_RELAY_URL=http://<relay-ip>:3000
supabase secrets set MIKROTIK_RELAY_SECRET=<relay-secret>
supabase secrets set FRONTEND_URL=http://<portal-ip>:8080supabase functions deployMigration 00013_setup_expire_sessions_cron_5min.sql already schedules the cron. Verify it exists:
SELECT * FROM pg_cron.job WHERE jobname = 'expire_sessions_every_5_minutes';If missing, run the migration directly:
supabase db push- Sign up at Paystack
- Switch to test mode
- Copy Public Key and Secret Key
- Add them to:
- Frontend
.env:VITE_PAYSTACK_PUBLIC_KEY - Supabase Secrets:
PAYSTACK_SECRET_KEY,PAYSTACK_PUBLIC_KEY
- Frontend
- Configure your Paystack callback URL to point to your deployed portal:
https://<your-domain>/payment/verify
- Set the webhook URL in Paystack dashboard:
https://<your-project>.functions.supabase.co/paystack-webhook
- Use a strong webhook secret and store it in Supabase Secrets as
PAYSTACK_WEBHOOK_SECRETif the code checks it.
The relay is needed because Supabase (cloud) cannot directly reach a MikroTik router behind CGNAT/Starlink. The relay sits on your LAN and forwards commands to the router's REST API.
Recommended:
- Raspberry Pi 4/5 on the same LAN as MikroTik
- An old laptop running Ubuntu/Debian
- An Android phone with Termux (see later section)
cd tasks/mikrotik-relay
npm install
cp .env.example .env
nano .envEdit .env:
PORT=3000
MIKROTIK_HOST=192.168.88.1
MIKROTIK_USER=admin
MIKROTIK_PASSWORD=your_router_password
RELAY_SECRET=a_long_random_secretnpm startOption A — Local device with public IP
- Open/forward port 3000 on your router
- Use
http://<public-ip>:3000asMIKROTIK_RELAY_URL
Option B — Cloudflare Tunnel (free, works behind CGNAT)
# Install cloudflared
# Then run:
cloudflared tunnel --url http://localhost:3000
# Use the https://*.trycloudflare.com URL as MIKROTIK_RELAY_URLOption C — Tailscale/VPN
- Run the relay on a VPS connected to the same Tailscale network as the router
supabase secrets set MIKROTIK_RELAY_URL=<relay-url>
supabase secrets set MIKROTIK_RELAY_SECRET=<relay-secret>- Winbox / WebFig → IP → Services → enable
www(port 80) orwww-ssl(port 443) - Create a dedicated API user:
- System → Users → Add
- Name:
api-user - Group: create a group with policies
read, write, api, hotspot
- Confirm hotspot server name under IP → Hotspot → Servers (default:
hotspot1)
- Build the static files:
pnpm buildThis creates a dist/ folder.
- Serve
dist/with nginx, Apache, Vercel, Netlify, or any static host.
Example nginx config:
server {
listen 80;
server_name portal.example.com;
root /var/www/netconnect/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}supabase functions deployRun as a systemd service or use PM2:
sudo npm install -g pm2
cd tasks/mikrotik-relay
pm2 start index.js --name mikrotik-relay
pm2 save
pm2 startup| Variable | Description |
|---|---|
VITE_SUPABASE_URL |
Supabase project URL |
VITE_SUPABASE_ANON_KEY |
Supabase public anon key |
VITE_PAYSTACK_PUBLIC_KEY |
Paystack public test/live key |
VITE_PAYSTACK_SECRET_KEY |
Paystack secret test/live key (used in dev flows) |
| Secret | Description |
|---|---|
PAYSTACK_SECRET_KEY |
Paystack server secret key |
PAYSTACK_PUBLIC_KEY |
Paystack public key |
SUPABASE_URL |
Same as VITE_SUPABASE_URL |
SUPABASE_SERVICE_ROLE_KEY |
Supabase service role key |
MIKROTIK_RELAY_URL |
URL of your LAN relay server |
MIKROTIK_RELAY_SECRET |
Shared secret for relay authentication |
FRONTEND_URL |
Customer portal URL for redirects |
PAYSTACK_WEBHOOK_SECRET |
Optional webhook verification secret |
| Variable | Description |
|---|---|
PORT |
Port the relay listens on |
MIKROTIK_HOST |
MikroTik router IP |
MIKROTIK_USER |
MikroTik API username |
MIKROTIK_PASSWORD |
MikroTik API password |
RELAY_SECRET |
Shared secret; must match Supabase secret |
After running migrations, a default admin user is created:
- Email:
admin@netconnect.local - Password:
admin
Change the password immediately after first login via Admin → Settings → Update Admin Credentials.
- Use
pnpminstead ofnpm - Increase Node memory:
NODE_OPTIONS=--max-old-space-size=4096 pnpm install - On low-end devices, build on a stronger machine and copy
dist/
- Ensure you ran
supabase linkfirst - Check secrets are set:
supabase secrets list - Check Deno type errors:
supabase functions build
- Verify
paystack-webhookis deployed - Verify Paystack webhook URL is correct
- Check Supabase Functions logs
- Check the voucher exists in Supabase
voucherstable - If using live sync, verify relay is running:
curl <relay>/health - Test MikroTik connectivity:
curl <relay>/mikrotik/test -H "x-relay-secret: ..."
- Confirm REST API service is enabled in RouterOS
- Confirm API user has
read, write, api, hotspotpolicies - Confirm
MIKROTIK_HOTSPOT_SERVERin relay matches actual server name - Check relay logs and Supabase Edge Function logs
- Confirm the portal host is on the same LAN/subnet as MikroTik
- Check firewall rules; ensure ports 8080 (portal) and 3000 (relay) are allowed
- Confirm MikroTik hotspot login page URL points to the portal IP
You can host both the static portal and the MikroTik relay on an Android phone using Termux. This is useful for low-budget setups where the phone sits on the same LAN as the MikroTik router.
- Android phone (e.g., Infinix Hot 9)
- Termux installed from F-Droid (not Play Store)
- Phone connected to MikroTik WiFi
pkg update && pkg upgrade -y
pkg install -y nodejs nginx openssh zip unzipBecause building on the phone is slow, build dist/ on your PC first (pnpm build), then copy dist/ and tasks/mikrotik-relay/ to the phone.
# On PC
zip -r portal-dist.zip dist/
zip -r relay.zip tasks/mikrotik-relay/
# Copy both to phone Download folder, then in Termux:
cp /sdcard/Download/portal-dist.zip ~/
cp /sdcard/Download/relay.zip ~/
unzip portal-dist.zip
unzip relay.zipcat > $PREFIX/etc/nginx/nginx.conf << 'EOF'
worker_processes 1;
events { worker_connections 256; }
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
server {
listen 8080;
root /data/data/com.termux/files/home/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
}
EOF
nginxcd ~/mikrotik-relay
npm install
# Edit .env, then:
node index.js &termux-wake-lockDisable Android battery optimization for Termux and consider installing Termux:Boot for auto-start on reboot.
supabase secrets set FRONTEND_URL=http://<phone-ip>:8080
supabase secrets set MIKROTIK_RELAY_URL=http://<phone-ip>:3000Set the hotspot login URL to:
http://<phone-ip>:8080
This project is provided as-is for educational and commercial use by the original purchaser. Redistribution without permission is not allowed.
For issues, refer to the Troubleshooting section or open an issue in your project's repository.