Modern, full-stack billing system for RTRW.NET ISP with proper WIB (Western Indonesia Time) timezone handling.
- ✅ Proper WIB Timezone Handling - All dates stored in UTC, displayed in WIB
- 🎨 Premium UI - Mobile-first responsive design with dark mode
- ⚡ Modern Stack - Next.js 15, TypeScript, Tailwind CSS, Prisma
- 🔐 Secure - Built-in authentication structure
- 📱 SPA Experience - Fast, smooth navigation without page reloads
- Framework: Next.js 15 (App Router)
- Language: TypeScript
- Styling: Tailwind CSS
- Database: MySQL with Prisma ORM
- Icons: Lucide React
- Date Handling: date-fns with timezone support
- Dashboard - Overview with stats and real-time data
- PPPoE Management - Users and profiles
- Hotspot Management - Vouchers, profiles, and templates
- Agent Management - Reseller accounts
- Invoices - Billing and payment tracking
- Payment Gateway - Multiple payment methods
- Keuangan - Financial reporting
- Sessions - Active connections monitoring
- WhatsApp Integration - Automated notifications
- Network Management - Router/NAS configuration
- Network Map - Visual network topology
- Settings - Company profile, cron jobs, GenieACS
This project solves the UTC vs WIB timezone issue that causes billing problems:
-
Database Storage (UTC)
- All dates stored in MySQL as UTC
- Prisma handles UTC storage automatically
-
Display (WIB)
- Frontend converts UTC to WIB using
date-fns-tz - Functions in
src/lib/timezone.ts:toWIB()- Convert UTC to WIB for displaytoUTC()- Convert WIB to UTC for storageformatWIB()- Format dates in WIBisExpired()- Check expiry in WIB context
- Frontend converts UTC to WIB using
-
Environment Configuration
TZ="Asia/Jakarta" NEXT_PUBLIC_TIMEZONE="Asia/Jakarta"
import { formatWIB, isExpired, toUTC } from '@/lib/timezone';
// Display date in WIB
const displayDate = formatWIB(user.createdAt, 'dd/MM/yyyy HH:mm');
// Check if expired (in WIB)
const expired = isExpired(user.expiredAt);
// Convert user input to UTC before saving
const utcDate = toUTC(userInputDate);
await prisma.user.create({ data: { expiredAt: utcDate } });Create MySQL database:
mysql -u root -p
CREATE DATABASE aibill_radius;
exit;Update .env with your database credentials:
DATABASE_URL="mysql://root:YOUR_PASSWORD@localhost:3306/aibill_radius?connection_limit=10&pool_timeout=20"
TZ="Asia/Jakarta"
NEXT_PUBLIC_TIMEZONE="Asia/Jakarta"npm install
npx prisma generate
npx prisma db pushImportant: This app integrates with FreeRADIUS and automatically restarts it when router/NAS configuration changes.
# Run automated setup script
bash scripts/setup-sudoers.sh
# Or manually:
sudo visudo -f /etc/sudoers.d/freeradius-restartAdd this line (replace gnetid with your PM2 user):
gnetid ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart freeradius
gnetid ALL=(ALL) NOPASSWD: /usr/bin/systemctl status freeradius
Save and test:
sudo systemctl restart freeradiusIf no password is asked, setup is successful! ✅
See SUDOERS_SETUP.md for detailed instructions.
npm run devOpen http://localhost:3000 - automatically redirects to /admin
# Build the app
npm run build
# Start with PM2
pm2 start npm --name "aibill-radius" -- start
# Or use ecosystem file (recommended)
pm2 start ecosystem.config.jssrc/
├── app/
│ ├── admin/ # Admin panel routes
│ │ ├── layout.tsx # Admin layout with sidebar
│ │ ├── page.tsx # Dashboard
│ │ ├── pppoe/ # PPPoE management
│ │ ├── hotspot/ # Hotspot management
│ │ └── ... # Other modules
│ └── page.tsx # Root (redirects to /admin)
├── lib/
│ ├── timezone.ts # WIB timezone utilities ⭐
│ └── utils.ts # General utilities
└── prisma/
└── schema.prisma # Database schema
- Sidebar Navigation - Collapsible, mobile-responsive
- Stats Cards - Real-time metrics display
- Data Tables - Sortable, filterable tables
- Forms - With validation and error handling
- Modals - For CRUD operations
- Dark Mode - Full dark mode support
- Environment variables for sensitive data
- Password hashing with bcryptjs
- SQL injection prevention via Prisma
- XSS protection built into Next.js
Core models included:
- Users (Admin, Agent, User roles)
- PPPoE Users & Profiles
- Hotspot Vouchers & Profiles
- Sessions (RADIUS accounting)
- Invoices & Payments
- Payment Gateways
- Routers/NAS
- WhatsApp Providers & Templates
- Company Settings
- Implement authentication (NextAuth.js)
- Add API routes for CRUD operations
- Integrate with RADIUS server
- Connect payment gateways (Midtrans, Xendit)
- WhatsApp API integration
- MikroTik API integration
- GenieACS integration for TR-069
- Add charts and analytics
- Export reports (PDF, Excel)
- Multi-language support
If you experience timezone issues:
-
Check environment variables:
echo $TZ # Should output: Asia/Jakarta
-
Verify in code:
import { getTimezoneInfo } from '@/lib/timezone'; console.log(getTimezoneInfo()); // Should show WIB info
-
Check database timezone:
SELECT @@global.time_zone, @@session.time_zone;
Private - Proprietary software for AIBILL RADIUS
Built with ❤️ for Indonesian ISPs with proper timezone handling.
Critical Note: Always use formatWIB() and toWIB() functions when displaying dates to users. Never display raw UTC dates from database.