feat: add 35+ features - i18n, voice input, gamification, driver tools, marketplace

- Multi-language support (English, Hindi, Tamil, Telugu) with icon-based UI
- Voice input (Web Speech API) for low-literacy users
- Driver tools: Ledger, Trip Planner, Return Load, Safety, Maintenance, FASTag
- Marketplace: WhatsApp share, Rate Intelligence, Classifieds, Fleet
- Engagement: Gamification (XP/Levels), Challenges, Leaderboard, Referrals, Feed
- Business: Invoice (GST+UPI), Reports+CSV, Notifications, Documents, Bank
- Games: Rate Guesser, Route Quiz
- SEO: Sitemap, public load share pages with OG tags
- India utilities: vehicle validation, UPI links, toll/fuel calculator
- 29 routes, 54 templates, 4 languages, 3 migration files
This commit is contained in:
Vivek 2026-05-31 09:19:16 +00:00
parent 60415a02fa
commit ed320e82c1
94 changed files with 3164 additions and 447 deletions

View file

@ -0,0 +1,44 @@
# Multi-Language Support with Icon-Based UI
## Overview
Add Hindi, English, Tamil, and Telugu language support to BharathTrucks with large icon-based buttons designed for low-literacy users.
## Requirements
1. Support 4 languages: Hindi (default), English, Tamil, Telugu
2. Language switcher in header — persisted in session
3. All navigation and action buttons use large icons with minimal text labels
4. Bottom nav and dashboard buttons are icon-first (large emoji/SVG + short label)
5. Low-literacy friendly: icons convey meaning without reading
## Technical Design
### i18n Approach
- Simple JSON translation files in `src/i18n/{lang}.json`
- Middleware reads `req.session.lang` (default: `hi`) and attaches `t()` helper to `res.locals`
- No external i18n library needed — lightweight custom implementation
### Language Switcher
- `GET /lang/:code` route sets `req.session.lang` and redirects back
- Header shows 4 flag/label buttons: हिंदी | EN | தமிழ் | తెలుగు
### Icon-Based UI for Low-Literacy Users
- Bottom nav: larger icons (40px), short label below
- Dashboard action buttons: icon-button class with 48px emoji + label
- All key actions identifiable by icon alone (🚛 truck, 📋 loads, 💰 money, etc.)
## Files Changed
- `src/i18n/hi.json` — Hindi translations
- `src/i18n/en.json` — English translations
- `src/i18n/ta.json` — Tamil translations
- `src/i18n/te.json` — Telugu translations
- `src/middleware/i18n.js` — language detection middleware
- `src/server.js` — add i18n middleware + `/lang/:code` route
- `src/views/partials/header.ejs` — language switcher
- `src/views/partials/bottom-nav.ejs` — icon-first nav
- `src/views/pages/driver-dashboard.ejs` — icon-based action buttons
- `src/public/css/govt-theme.css` — icon-button styles
## Testing
- Switch language via header buttons, verify all labels change
- Verify icons are large and clear on mobile
- Verify session persists language across page navigations

143
webapp/src/i18n/en.json Normal file
View file

@ -0,0 +1,143 @@
{
"nav": {
"home": "Home",
"loads": "Loads",
"post": "Post",
"trips": "Trips",
"messages": "Messages",
"profile": "Profile"
},
"actions": {
"viewLoads": "View Loads",
"myTrips": "My Trips",
"earnings": "Earnings",
"postLoad": "Post Load",
"bid": "Place Bid",
"search": "Search",
"login": "Login",
"register": "Register",
"logout": "Logout"
},
"dashboard": {
"hello": "Hello",
"totalTrips": "Total Trips",
"activeBids": "Active Bids",
"earnings": "Earnings",
"activeTrips": "Active Trips",
"shipperTitle": "Shipper Dashboard",
"brokerTitle": "Broker Dashboard",
"myLoads": "My Loads",
"openLoads": "Open Loads",
"activeShipments": "Active Shipments",
"recentLoads": "Recent Loads",
"loadsPosted": "Loads Posted",
"deals": "Deals"
},
"common": {
"appName": "Bharath Trucks",
"subtitle": "National Freight Platform",
"noLoads": "No loads available",
"urgent": "Urgent",
"from": "From",
"to": "To",
"truckType": "Truck Type",
"all": "All",
"loadboard": "Load Board",
"tons": "tons",
"bids": "bids"
},
"auth": {
"loginTitle": "Login",
"loginSubtitle": "Enter your username and password",
"registerTitle": "Register",
"registerSubtitle": "Create a free account",
"username": "Username",
"password": "Password",
"confirmPassword": "Confirm Password",
"fullName": "Full Name",
"phone": "Phone Number",
"yourRole": "Your Role",
"driver": "Driver",
"shipper": "Shipper",
"broker": "Broker",
"noAccount": "New here?",
"hasAccount": "Already have an account?",
"registerBtn": "Register Free",
"vehicleNumber": "Vehicle Number",
"vehicleHint": "Your vehicle number will be your username"
},
"trips": {
"noTrips": "No trips yet",
"pickedUp": "Picked Up",
"inTransit": "In Transit",
"delivered": "Delivered"
},
"postLoad": {
"weight": "Weight (tons)",
"material": "Material Type",
"budget": "Budget (₹)",
"pickupDate": "Pickup Date",
"notes": "Notes",
"select": "Select"
},
"profile": {
"name": "Name",
"phone": "Phone",
"city": "City",
"state": "State",
"update": "Update Profile",
"updated": "Profile updated",
"myLevel": "My Level"
},
"messages": {
"noMessages": "No messages yet",
"typeHere": "Type message..."
},
"loadDetail": {
"yourPrice": "Your Price",
"updateBid": "Update Bid",
"bidsReceived": "Bids Received"
},
"landing": {
"badge": "Government Registered Platform 🇮🇳",
"heroTitle": "Truck Drivers. Shippers. Brokers.",
"heroHighlight": "Free for everyone.",
"heroSub": "India's National Freight Platform — Post loads, bid, earn. No fees ever.",
"free": "Free",
"forever": "Forever",
"seconds": "seconds",
"minutes": "minutes",
"firstBid": "First Bid",
"onePlatform": "One Platform. Three Users.",
"onePlatformSub": "Whether you ship goods, drive trucks, or broker deals — Bharath Trucks is for you.",
"driverF1": "Find loads and place bids",
"driverF2": "Avoid empty returns",
"driverF3": "Track your earnings",
"driverF4": "Connect directly with shippers",
"shipperF1": "Post loads, get bids",
"shipperF2": "Choose verified drivers",
"shipperF3": "Track shipment status",
"shipperF4": "Keep payment records",
"brokerF1": "Digitize your network",
"brokerF2": "Track commissions",
"brokerF3": "Post loads for shippers",
"brokerF4": "Grow driver network",
"howTitle": "How does it work?",
"howSub": "Just 4 easy steps",
"step1": "Register",
"step1Desc": "Create a free account with your phone number. Choose your role.",
"step2": "Post / Find Loads",
"step2Desc": "Shippers post loads. Drivers browse available loads.",
"step3": "Bid / Accept",
"step3Desc": "Drivers quote their price. Shippers pick the best bid.",
"step4": "Deliver & Get Paid",
"step4Desc": "Complete the trip. Get paid directly via UPI.",
"whyTitle": "Why Bharath Trucks?",
"noFee": "No fees",
"secure": "Secure platform",
"mobile": "Works on mobile",
"madeInIndia": "Made for India",
"ctaTitle": "Start today — completely free!",
"ctaSub": "All features free for 1000+ users. No credit card needed."
}
}

143
webapp/src/i18n/hi.json Normal file
View file

@ -0,0 +1,143 @@
{
"nav": {
"home": "होम",
"loads": "लोड",
"post": "पोस्ट",
"trips": "ट्रिप",
"messages": "संदेश",
"profile": "प्रोफ़ाइल"
},
"actions": {
"viewLoads": "लोड देखें",
"myTrips": "मेरी ट्रिप",
"earnings": "कमाई",
"postLoad": "लोड पोस्ट करें",
"bid": "बोली लगाएं",
"search": "खोजें",
"login": "लॉगिन",
"register": "पंजीकरण",
"logout": "लॉगआउट"
},
"dashboard": {
"hello": "नमस्ते",
"totalTrips": "कुल ट्रिप",
"activeBids": "सक्रिय बोलियाँ",
"earnings": "कमाई",
"activeTrips": "सक्रिय ट्रिप",
"shipperTitle": "शिपर डैशबोर्ड",
"brokerTitle": "ब्रोकर डैशबोर्ड",
"myLoads": "मेरे लोड",
"openLoads": "खुले लोड",
"activeShipments": "सक्रिय शिपमेंट",
"recentLoads": "हाल के लोड",
"loadsPosted": "लोड पोस्ट",
"deals": "सौदे"
},
"common": {
"appName": "भारत ट्रक्स",
"subtitle": "राष्ट्रीय माल परिवहन मंच",
"noLoads": "कोई लोड उपलब्ध नहीं",
"urgent": "अर्जेंट",
"from": "कहाँ से",
"to": "कहाँ तक",
"truckType": "ट्रक प्रकार",
"all": "सभी",
"loadboard": "लोड बोर्ड",
"tons": "टन",
"bids": "बोली"
},
"auth": {
"loginTitle": "लॉगिन | Login",
"loginSubtitle": "अपना यूज़रनेम और पासवर्ड दर्ज करें",
"registerTitle": "पंजीकरण | Register",
"registerSubtitle": "मुफ्त खाता बनाएं",
"username": "यूज़रनेम",
"password": "पासवर्ड",
"confirmPassword": "पासवर्ड पुष्टि",
"fullName": "पूरा नाम",
"phone": "फोन नंबर",
"yourRole": "आप कौन हैं?",
"driver": "ड्राइवर",
"shipper": "शिपर",
"broker": "ब्रोकर",
"noAccount": "नया खाता?",
"hasAccount": "पहले से खाता है?",
"registerBtn": "मुफ्त पंजीकरण करें",
"vehicleNumber": "गाड़ी नंबर",
"vehicleHint": "आपका गाड़ी नंबर ही आपका यूज़रनेम होगा"
},
"trips": {
"noTrips": "कोई ट्रिप नहीं",
"pickedUp": "पिकअप किया",
"inTransit": "रास्ते में",
"delivered": "पहुँचा दिया"
},
"postLoad": {
"weight": "वज़न (टन)",
"material": "माल का प्रकार",
"budget": "बजट (₹)",
"pickupDate": "पिकअप तारीख",
"notes": "विवरण",
"select": "चुनें"
},
"profile": {
"name": "नाम",
"phone": "फोन नंबर",
"city": "शहर",
"state": "राज्य",
"update": "प्रोफ़ाइल अपडेट करें",
"updated": "प्रोफ़ाइल अपडेट हो गई",
"myLevel": "मेरा लेवल"
},
"messages": {
"noMessages": "कोई संदेश नहीं",
"typeHere": "संदेश लिखें..."
},
"loadDetail": {
"yourPrice": "आपकी कीमत",
"updateBid": "बोली अपडेट करें",
"bidsReceived": "बोलियाँ प्राप्त"
},
"landing": {
"badge": "भारत सरकार पंजीकृत मंच | Registered Platform",
"heroTitle": "ट्रक ड्राइवर। शिपर। ब्रोकर।",
"heroHighlight": "सबके लिए मुफ्त।",
"heroSub": "भारत का राष्ट्रीय माल परिवहन मंच — लोड पोस्ट करें, बोली लगाएं, कमाई करें। बिना किसी शुल्क के।",
"free": "मुफ्त",
"forever": "हमेशा के लिए",
"seconds": "सेकंड",
"minutes": "मिनट",
"firstBid": "पहली बोली",
"onePlatform": "एक मंच। तीन उपयोगकर्ता।",
"onePlatformSub": "चाहे आप माल भेजें, ट्रक चलाएं, या सौदे कराएं — भारत ट्रक्स आपके लिए है।",
"driverF1": "लोड खोजें और बोली लगाएं",
"driverF2": "खाली वापसी से बचें",
"driverF3": "कमाई का हिसाब रखें",
"driverF4": "सीधे शिपर से जुड़ें",
"shipperF1": "लोड पोस्ट करें, बोली पाएं",
"shipperF2": "सत्यापित ड्राइवर चुनें",
"shipperF3": "माल की स्थिति जानें",
"shipperF4": "भुगतान का रिकॉर्ड रखें",
"brokerF1": "अपने नेटवर्क को डिजिटल करें",
"brokerF2": "कमीशन ट्रैक करें",
"brokerF3": "शिपर के लिए लोड पोस्ट करें",
"brokerF4": "ड्राइवर नेटवर्क बढ़ाएं",
"howTitle": "कैसे काम करता है?",
"howSub": "सिर्फ 4 आसान कदम",
"step1": "पंजीकरण करें",
"step1Desc": "फोन नंबर से मुफ्त अकाउंट बनाएं। अपनी भूमिका चुनें।",
"step2": "लोड पोस्ट / खोजें",
"step2Desc": "शिपर लोड पोस्ट करें। ड्राइवर उपलब्ध लोड देखें।",
"step3": "बोली लगाएं / स्वीकार करें",
"step3Desc": "ड्राइवर अपनी कीमत बताएं। शिपर सबसे अच्छी बोली चुनें।",
"step4": "माल पहुँचाएं, भुगतान पाएं",
"step4Desc": "ट्रिप पूरी करें। UPI से सीधे भुगतान पाएं।",
"whyTitle": "क्यों भारत ट्रक्स?",
"noFee": "कोई शुल्क नहीं",
"secure": "सुरक्षित मंच",
"mobile": "मोबाइल पर चलता है",
"madeInIndia": "भारत के लिए बना",
"ctaTitle": "आज ही शुरू करें — बिल्कुल मुफ्त!",
"ctaSub": "1000+ उपयोगकर्ताओं तक सभी सुविधाएं मुफ्त। कोई क्रेडिट कार्ड नहीं चाहिए।"
}
}

143
webapp/src/i18n/ta.json Normal file
View file

@ -0,0 +1,143 @@
{
"nav": {
"home": "முகப்பு",
"loads": "சரக்கு",
"post": "பதிவு",
"trips": "பயணம்",
"messages": "செய்தி",
"profile": "சுயவிவரம்"
},
"actions": {
"viewLoads": "சரக்கு பார்க்க",
"myTrips": "என் பயணங்கள்",
"earnings": "வருமானம்",
"postLoad": "சரக்கு பதிவு",
"bid": "ஏலம்",
"search": "தேடு",
"login": "உள்நுழை",
"register": "பதிவு செய்",
"logout": "வெளியேறு"
},
"dashboard": {
"hello": "வணக்கம்",
"totalTrips": "மொத்த பயணம்",
"activeBids": "செயலில் ஏலங்கள்",
"earnings": "வருமானம்",
"activeTrips": "செயலில் பயணம்",
"shipperTitle": "அனுப்புநர் டாஷ்போர்டு",
"brokerTitle": "தரகர் டாஷ்போர்டு",
"myLoads": "என் சரக்கு",
"openLoads": "திறந்த சரக்கு",
"activeShipments": "செயலில் ஷிப்மென்ட்",
"recentLoads": "சமீபத்திய சரக்கு",
"loadsPosted": "பதிவு செய்தவை",
"deals": "ஒப்பந்தங்கள்"
},
"common": {
"appName": "பாரத் டிரக்ஸ்",
"subtitle": "தேசிய சரக்கு போக்குவரத்து தளம்",
"noLoads": "சரக்கு இல்லை",
"urgent": "அவசரம்",
"from": "எங்கிருந்து",
"to": "எங்கு",
"truckType": "லாரி வகை",
"all": "அனைத்தும்",
"loadboard": "சரக்கு பலகை",
"tons": "டன்",
"bids": "ஏலங்கள்"
},
"auth": {
"loginTitle": "உள்நுழை",
"loginSubtitle": "உங்கள் பயனர்பெயர் மற்றும் கடவுச்சொல்லை உள்ளிடவும்",
"registerTitle": "பதிவு செய்",
"registerSubtitle": "இலவச கணக்கு உருவாக்கவும்",
"username": "பயனர்பெயர்",
"password": "கடவுச்சொல்",
"confirmPassword": "கடவுச்சொல் உறுதி",
"fullName": "முழு பெயர்",
"phone": "தொலைபேசி எண்",
"yourRole": "நீங்கள் யார்?",
"driver": "டிரைவர்",
"shipper": "அனுப்புநர்",
"broker": "தரகர்",
"noAccount": "புதிய கணக்கு?",
"hasAccount": "ஏற்கனவே கணக்கு உள்ளதா?",
"registerBtn": "இலவச பதிவு",
"vehicleNumber": "வாகன எண்",
"vehicleHint": "உங்கள் வாகன எண் உங்கள் பயனர்பெயர் ஆகும்"
},
"trips": {
"noTrips": "பயணங்கள் இல்லை",
"pickedUp": "எடுக்கப்பட்டது",
"inTransit": "வழியில்",
"delivered": "வழங்கப்பட்டது"
},
"postLoad": {
"weight": "எடை (டன்)",
"material": "பொருள் வகை",
"budget": "பட்ஜெட் (₹)",
"pickupDate": "பிக்அப் தேதி",
"notes": "குறிப்புகள்",
"select": "தேர்வு"
},
"profile": {
"name": "பெயர்",
"phone": "தொலைபேசி",
"city": "நகரம்",
"state": "மாநிலம்",
"update": "புரொஃபைல் புதுப்பி",
"updated": "புரொஃபைல் புதுப்பிக்கப்பட்டது",
"myLevel": "என் நிலை"
},
"messages": {
"noMessages": "செய்திகள் இல்லை",
"typeHere": "செய்தி எழுதுங்கள்..."
},
"loadDetail": {
"yourPrice": "உங்கள் விலை",
"updateBid": "ஏலம் புதுப்பி",
"bidsReceived": "ஏலங்கள் பெறப்பட்டன"
},
"landing": {
"badge": "அரசு பதிவு செய்யப்பட்ட தளம் 🇮🇳",
"heroTitle": "டிரக் டிரைவர். அனுப்புநர். தரகர்.",
"heroHighlight": "அனைவருக்கும் இலவசம்.",
"heroSub": "இந்தியாவின் தேசிய சரக்கு தளம் — சரக்கு பதிவு செய்யுங்கள், ஏலம் விடுங்கள், சம்பாதியுங்கள். கட்டணம் இல்லை.",
"free": "இலவசம்",
"forever": "எப்போதும்",
"seconds": "வினாடி",
"minutes": "நிமிடம்",
"firstBid": "முதல் ஏலம்",
"onePlatform": "ஒரு தளம். மூன்று பயனர்கள்.",
"onePlatformSub": "நீங்கள் சரக்கு அனுப்பினாலும், லாரி ஓட்டினாலும், தரகு செய்தாலும் — பாரத் டிரக்ஸ் உங்களுக்கானது.",
"driverF1": "சரக்கு கண்டுபிடித்து ஏலம் விடுங்கள்",
"driverF2": "வெற்று திரும்புதலை தவிர்க்கவும்",
"driverF3": "வருமானத்தை கணக்கிடுங்கள்",
"driverF4": "நேரடியாக அனுப்புநருடன் இணையுங்கள்",
"shipperF1": "சரக்கு பதிவு செய்யுங்கள், ஏலம் பெறுங்கள்",
"shipperF2": "சரிபார்க்கப்பட்ட டிரைவரை தேர்வு செய்யுங்கள்",
"shipperF3": "சரக்கு நிலையை அறியுங்கள்",
"shipperF4": "பணம் செலுத்தல் பதிவு வைக்கவும்",
"brokerF1": "உங்கள் நெட்வொர்க்கை டிஜிட்டல் ஆக்குங்கள்",
"brokerF2": "கமிஷன் கண்காணிக்கவும்",
"brokerF3": "அனுப்புநருக்காக சரக்கு பதிவு செய்யுங்கள்",
"brokerF4": "டிரைவர் நெட்வொர்க் வளர்க்கவும்",
"howTitle": "எப்படி வேலை செய்கிறது?",
"howSub": "வெறும் 4 எளிய படிகள்",
"step1": "பதிவு செய்யுங்கள்",
"step1Desc": "தொலைபேசி எண்ணுடன் இலவச கணக்கு உருவாக்கவும். உங்கள் பங்கை தேர்வு செய்யுங்கள்.",
"step2": "சரக்கு பதிவு / தேடு",
"step2Desc": "அனுப்புநர் சரக்கு பதிவு செய்யுங்கள். டிரைவர் கிடைக்கும் சரக்கை பாருங்கள்.",
"step3": "ஏலம் / ஏற்றுக்கொள்",
"step3Desc": "டிரைவர் விலை சொல்லுங்கள். அனுப்புநர் சிறந்த ஏலத்தை தேர்வு செய்யுங்கள்.",
"step4": "டெலிவரி & பணம் பெறுங்கள்",
"step4Desc": "பயணத்தை முடியுங்கள். UPI மூலம் நேரடியாக பணம் பெறுங்கள்.",
"whyTitle": "ஏன் பாரத் டிரக்ஸ்?",
"noFee": "கட்டணம் இல்லை",
"secure": "பாதுகாப்பான தளம்",
"mobile": "மொபைலில் இயங்கும்",
"madeInIndia": "இந்தியாவுக்காக உருவாக்கப்பட்டது",
"ctaTitle": "இன்றே தொடங்குங்கள் — முற்றிலும் இலவசம்!",
"ctaSub": "1000+ பயனர்களுக்கு அனைத்து வசதிகளும் இலவசம். கிரெடிட் கார்டு தேவையில்லை."
}
}

143
webapp/src/i18n/te.json Normal file
View file

@ -0,0 +1,143 @@
{
"nav": {
"home": "హోమ్",
"loads": "లోడ్లు",
"post": "పోస్ట్",
"trips": "ట్రిప్",
"messages": "సందేశాలు",
"profile": "ప్రొఫైల్"
},
"actions": {
"viewLoads": "లోడ్లు చూడండి",
"myTrips": "నా ట్రిప్‌లు",
"earnings": "ఆదాయం",
"postLoad": "లోడ్ పోస్ట్",
"bid": "బిడ్ వేయండి",
"search": "వెతకండి",
"login": "లాగిన్",
"register": "నమోదు",
"logout": "లాగ్అవుట్"
},
"dashboard": {
"hello": "నమస్కారం",
"totalTrips": "మొత్తం ట్రిప్‌లు",
"activeBids": "యాక్టివ్ బిడ్లు",
"earnings": "ఆదాయం",
"activeTrips": "యాక్టివ్ ట్రిప్‌లు",
"shipperTitle": "షిప్పర్ డాష్‌బోర్డ్",
"brokerTitle": "బ్రోకర్ డాష్‌బోర్డ్",
"myLoads": "నా లోడ్లు",
"openLoads": "ఓపెన్ లోడ్లు",
"activeShipments": "యాక్టివ్ షిప్‌మెంట్లు",
"recentLoads": "ఇటీవలి లోడ్లు",
"loadsPosted": "పోస్ట్ చేసిన లోడ్లు",
"deals": "డీల్స్"
},
"common": {
"appName": "భారత్ ట్రక్స్",
"subtitle": "జాతీయ సరుకు రవాణా వేదిక",
"noLoads": "లోడ్లు లేవు",
"urgent": "అత్యవసరం",
"from": "ఎక్కడ నుండి",
"to": "ఎక్కడికి",
"truckType": "ట్రక్ రకం",
"all": "అన్నీ",
"loadboard": "లోడ్ బోర్డ్",
"tons": "టన్నులు",
"bids": "బిడ్లు"
},
"auth": {
"loginTitle": "లాగిన్",
"loginSubtitle": "మీ యూజర్‌నేమ్ మరియు పాస్‌వర్డ్ నమోదు చేయండి",
"registerTitle": "నమోదు",
"registerSubtitle": "ఉచిత ఖాతా సృష్టించండి",
"username": "యూజర్‌నేమ్",
"password": "పాస్‌వర్డ్",
"confirmPassword": "పాస్‌వర్డ్ నిర్ధారణ",
"fullName": "పూర్తి పేరు",
"phone": "ఫోన్ నంబర్",
"yourRole": "మీరు ఎవరు?",
"driver": "డ్రైవర్",
"shipper": "షిప్పర్",
"broker": "బ్రోకర్",
"noAccount": "కొత్త ఖాతా?",
"hasAccount": "ఇప్పటికే ఖాతా ఉందా?",
"registerBtn": "ఉచిత నమోదు",
"vehicleNumber": "వాహన నంబర్",
"vehicleHint": "మీ వాహన నంబర్ మీ యూజర్‌నేమ్ అవుతుంది"
},
"trips": {
"noTrips": "ట్రిప్‌లు లేవు",
"pickedUp": "పికప్ చేసారు",
"inTransit": "దారిలో",
"delivered": "డెలివరీ అయింది"
},
"postLoad": {
"weight": "బరువు (టన్నులు)",
"material": "మెటీరియల్ రకం",
"budget": "బడ్జెట్ (₹)",
"pickupDate": "పికప్ తేదీ",
"notes": "నోట్స్",
"select": "ఎంచుకోండి"
},
"profile": {
"name": "పేరు",
"phone": "ఫోన్",
"city": "నగరం",
"state": "రాష్ట్రం",
"update": "ప్రొఫైల్ అప్‌డేట్",
"updated": "ప్రొఫైల్ అప్‌డేట్ అయింది",
"myLevel": "నా లెవెల్"
},
"messages": {
"noMessages": "సందేశాలు లేవు",
"typeHere": "సందేశం రాయండి..."
},
"loadDetail": {
"yourPrice": "మీ ధర",
"updateBid": "బిడ్ అప్‌డేట్",
"bidsReceived": "బిడ్లు వచ్చాయి"
},
"landing": {
"badge": "ప్రభుత్వ నమోదిత వేదిక 🇮🇳",
"heroTitle": "ట్రక్ డ్రైవర్లు. షిప్పర్లు. బ్రోకర్లు.",
"heroHighlight": "అందరికీ ఉచితం.",
"heroSub": "భారతదేశ జాతీయ సరుకు రవాణా వేదిక — లోడ్ పోస్ట్ చేయండి, బిడ్ వేయండి, సంపాదించండి. ఎటువంటి ఫీజు లేదు.",
"free": "ఉచితం",
"forever": "ఎప్పటికీ",
"seconds": "సెకన్లు",
"minutes": "నిమిషాలు",
"firstBid": "మొదటి బిడ్",
"onePlatform": "ఒక వేదిక. ముగ్గురు వినియోగదారులు.",
"onePlatformSub": "మీరు సరుకు పంపినా, ట్రక్ నడిపినా, డీల్స్ చేసినా — భారత్ ట్రక్స్ మీ కోసం.",
"driverF1": "లోడ్లు కనుగొని బిడ్ వేయండి",
"driverF2": "ఖాళీ తిరుగు ప్రయాణం నివారించండి",
"driverF3": "ఆదాయం లెక్కించండి",
"driverF4": "నేరుగా షిప్పర్‌తో కనెక్ట్ అవ్వండి",
"shipperF1": "లోడ్ పోస్ట్ చేయండి, బిడ్లు పొందండి",
"shipperF2": "వెరిఫైడ్ డ్రైవర్‌ను ఎంచుకోండి",
"shipperF3": "సరుకు స్థితి తెలుసుకోండి",
"shipperF4": "చెల్లింపు రికార్డ్ ఉంచండి",
"brokerF1": "మీ నెట్‌వర్క్‌ను డిజిటల్ చేయండి",
"brokerF2": "కమీషన్ ట్రాక్ చేయండి",
"brokerF3": "షిప్పర్ కోసం లోడ్ పోస్ట్ చేయండి",
"brokerF4": "డ్రైవర్ నెట్‌వర్క్ పెంచండి",
"howTitle": "ఎలా పని చేస్తుంది?",
"howSub": "కేవలం 4 సులభ అడుగులు",
"step1": "నమోదు చేయండి",
"step1Desc": "ఫోన్ నంబర్‌తో ఉచిత ఖాతా సృష్టించండి. మీ పాత్ర ఎంచుకోండి.",
"step2": "లోడ్ పోస్ట్ / వెతకండి",
"step2Desc": "షిప్పర్ లోడ్ పోస్ట్ చేయండి. డ్రైవర్ అందుబాటులో ఉన్న లోడ్లు చూడండి.",
"step3": "బిడ్ / అంగీకరించండి",
"step3Desc": "డ్రైవర్ మీ ధర చెప్పండి. షిప్పర్ ఉత్తమ బిడ్ ఎంచుకోండి.",
"step4": "డెలివరీ & చెల్లింపు పొందండి",
"step4Desc": "ట్రిప్ పూర్తి చేయండి. UPI ద్వారా నేరుగా చెల్లింపు పొందండి.",
"whyTitle": "ఎందుకు భారత్ ట్రక్స్?",
"noFee": "ఫీజు లేదు",
"secure": "సురక్షిత వేదిక",
"mobile": "మొబైల్‌లో పని చేస్తుంది",
"madeInIndia": "భారతదేశం కోసం తయారు",
"ctaTitle": "ఈ రోజే ప్రారంభించండి — పూర్తిగా ఉచితం!",
"ctaSub": "1000+ వినియోగదారులకు అన్ని ఫీచర్లు ఉచితం. క్రెడిట్ కార్డ్ అవసరం లేదు."
}
}

View file

@ -0,0 +1,46 @@
// Gamification Engine: XP, Levels, Achievements, Badges, Streaks
const XP_REWARDS = {
signup: 50, add_phone: 30, select_role: 25, add_vehicle: 40, complete_onboarding: 100,
first_login_today: 10, post_load: 30, place_bid: 20, win_bid: 50, complete_trip: 40,
add_ledger_entry: 15, settle_payment: 25, share_load_whatsapp: 15,
invite_friend: 40, friend_joined: 60, receive_rating: 20, give_rating: 10,
safety_checkin: 5, log_toll: 5, add_reminder: 10,
first_load_posted: 75, first_bid_placed: 75, first_bid_won: 100, first_trip_completed: 100,
login_streak_3: 50, login_streak_7: 150, login_streak_30: 500,
};
const LEVELS = [
{ level: 1, xp: 0, title: 'Beginner', title_hi: 'शुरुआत', icon: '🌱' },
{ level: 2, xp: 100, title: 'Starter', title_hi: 'नया', icon: '🌿' },
{ level: 3, xp: 300, title: 'Active', title_hi: 'सक्रिय', icon: '🌳' },
{ level: 4, xp: 600, title: 'Regular', title_hi: 'नियमित', icon: '⭐' },
{ level: 5, xp: 1000, title: 'Pro', title_hi: 'प्रो', icon: '🌟' },
{ level: 6, xp: 1500, title: 'Expert', title_hi: 'विशेषज्ञ', icon: '💫' },
{ level: 7, xp: 2500, title: 'Master', title_hi: 'मास्टर', icon: '🏆' },
{ level: 8, xp: 4000, title: 'Legend', title_hi: 'लीजेंड', icon: '👑' },
];
const ACHIEVEMENTS = [
{ id: 'first_load', title: 'First Load', icon: '📦', xp: 75, condition: 'post_load >= 1' },
{ id: 'first_bid', title: 'First Bid', icon: '🏷️', xp: 75, condition: 'place_bid >= 1' },
{ id: 'first_trip', title: 'First Trip', icon: '🚛', xp: 100, condition: 'complete_trip >= 1' },
{ id: 'five_trips', title: '5 Trips', icon: '🎯', xp: 150, condition: 'complete_trip >= 5' },
{ id: 'ten_trips', title: '10 Trips', icon: '🔥', xp: 250, condition: 'complete_trip >= 10' },
{ id: 'social_butterfly', title: 'Social', icon: '🦋', xp: 100, condition: 'share_load_whatsapp >= 5' },
{ id: 'safe_driver', title: 'Safe Driver', icon: '🛡️', xp: 100, condition: 'safety_checkin >= 7' },
{ id: 'streak_week', title: '7 Day Streak', icon: '🔥', xp: 150, condition: 'login_streak >= 7' },
{ id: 'referrer', title: 'Referrer', icon: '🤝', xp: 200, condition: 'invite_friend >= 3' },
];
function getLevelForXP(xp) {
let current = LEVELS[0];
for (const l of LEVELS) { if (xp >= l.xp) current = l; else break; }
const next = LEVELS[current.level] || null;
const progress = next ? Math.round(((xp - current.xp) / (next.xp - current.xp)) * 100) : 100;
return { ...current, xp_current: xp, xp_next: next ? next.xp : current.xp, progress };
}
function getXPReward(action) { return XP_REWARDS[action] || 0; }
module.exports = { XP_REWARDS, LEVELS, ACHIEVEMENTS, getLevelForXP, getXPReward };

96
webapp/src/lib/india.js Normal file
View file

@ -0,0 +1,96 @@
// India-specific utilities: UPI, vehicle validation, toll calc, fuel, WhatsApp templates
const STATES = {
KL:'Kerala',TN:'Tamil Nadu',KA:'Karnataka',AP:'Andhra Pradesh',TS:'Telangana',
MH:'Maharashtra',GJ:'Gujarat',RJ:'Rajasthan',UP:'Uttar Pradesh',DL:'Delhi',
HR:'Haryana',PB:'Punjab',WB:'West Bengal',MP:'Madhya Pradesh',CG:'Chhattisgarh',
JH:'Jharkhand',BR:'Bihar',OR:'Odisha',GA:'Goa',HP:'Himachal Pradesh',
};
const DIESEL_PRICES = {KL:95.5,TN:92.8,KA:92.4,MH:92.1,TS:95.6,AP:95.2,GJ:92.3,DL:90.6,UP:92.8,RJ:93.1,DEFAULT:92.5};
const FREIGHT_CITIES = [
'Mumbai','Delhi','Bangalore','Chennai','Hyderabad','Pune','Ahmedabad','Kolkata',
'Jaipur','Lucknow','Nagpur','Indore','Surat','Nashik','Coimbatore','Madurai',
'Vijayawada','Visakhapatnam','Kochi','Thiruvananthapuram','Mangalore','Hubli',
'Salem','Erode','Vellore','Tirupati','Guntur','Rajkot','Vadodara','Bhopal',
];
const ROUTE_DB = {
'mumbai_delhi':{km:1400,hours:22,tolls:12},'mumbai_pune':{km:150,hours:3,tolls:2},
'mumbai_ahmedabad':{km:530,hours:8,tolls:4},'mumbai_bangalore':{km:980,hours:16,tolls:8},
'delhi_jaipur':{km:280,hours:5,tolls:3},'delhi_lucknow':{km:560,hours:9,tolls:5},
'delhi_kolkata':{km:1500,hours:24,tolls:12},'bangalore_chennai':{km:350,hours:6,tolls:3},
'bangalore_hyderabad':{km:570,hours:9,tolls:5},'chennai_hyderabad':{km:630,hours:10,tolls:5},
'chennai_mumbai':{km:1330,hours:22,tolls:10},'kolkata_delhi':{km:1500,hours:24,tolls:12},
'pune_nagpur':{km:720,hours:12,tolls:6},'ahmedabad_mumbai':{km:530,hours:8,tolls:4},
'hyderabad_bangalore':{km:570,hours:9,tolls:5},'indore_mumbai':{km:590,hours:10,tolls:5},
'jaipur_delhi':{km:280,hours:5,tolls:3},'lucknow_delhi':{km:560,hours:9,tolls:5},
'surat_mumbai':{km:300,hours:5,tolls:3},'nagpur_mumbai':{km:840,hours:14,tolls:7},
'coimbatore_chennai':{km:500,hours:8,tolls:4},'kochi_bangalore':{km:550,hours:10,tolls:4},
'vijayawada_hyderabad':{km:270,hours:5,tolls:3},'madurai_chennai':{km:460,hours:8,tolls:4},
};
function validateVehicleNumber(number) {
if (!number) return { valid: false, error: 'Required' };
const cleaned = number.replace(/[\s\-\.]/g, '').toUpperCase();
if (!/^[A-Z]{2}\d{1,2}[A-Z]{1,3}\d{4}$/.test(cleaned)) return { valid: false, error: 'Invalid format' };
const sc = cleaned.substring(0, 2);
return { valid: true, state_code: sc, state_name: STATES[sc] || 'Unknown', formatted: `${cleaned.substring(0,2)} ${cleaned.substring(2,4)} ${cleaned.substring(4,cleaned.length-4)} ${cleaned.slice(-4)}` };
}
function formatIndianPhone(phone) {
if (!phone) return null;
let c = phone.replace(/[\s\-\(\)\+]/g, '');
if (c.startsWith('0')) c = c.substring(1);
if (c.startsWith('91') && c.length === 12) c = c.substring(2);
if (c.length !== 10) return { valid: false };
return { valid: true, formatted: `+91 ${c.substring(0,5)} ${c.substring(5)}`, whatsapp: `91${c}`, raw: c };
}
function generateUPILink(opts) {
if (!opts.upi_id) return null;
const p = new URLSearchParams({ pa: opts.upi_id, pn: opts.name || 'BharathTrucks', am: String(opts.amount || ''), cu: 'INR', tn: opts.note || 'Freight Payment' });
return { upi_intent: `upi://pay?${p}`, gpay: `tez://upi/pay?${p}`, phonepe: `phonepe://pay?${p}` };
}
function formatINR(n) { return '₹' + (parseFloat(n) || 0).toLocaleString('en-IN'); }
function estimateToll(distanceKm, vehicleType = 'truck') {
const rates = { lcv: 1.5, truck: 2.5, multi_axle: 3.5 };
return Math.round(distanceKm * 0.6 * (rates[vehicleType] || 2.5));
}
function calculateTripCost(params) {
const { distance_km, vehicle_type, origin_state, dest_state, freight_charged, mileage } = params;
const m = mileage || 4;
const avgDiesel = ((DIESEL_PRICES[origin_state] || DIESEL_PRICES.DEFAULT) + (DIESEL_PRICES[dest_state] || DIESEL_PRICES.DEFAULT)) / 2;
const fuelLitres = Math.round(distance_km / m);
const fuelCost = Math.round(fuelLitres * avgDiesel);
const toll = estimateToll(distance_km, vehicle_type);
const driverBata = Math.round(distance_km * 1.5);
const misc = 800;
const total = fuelCost + toll + driverBata + misc;
const profit = (freight_charged || 0) - total;
return { fuel: { litres: fuelLitres, price: avgDiesel, cost: fuelCost }, toll, driver_bata: driverBata, misc, total, profit, margin: freight_charged ? Math.round((profit / freight_charged) * 100) : 0, viable: profit > 0 };
}
function getRouteInfo(origin, destination) {
const o = origin.toLowerCase().trim().replace(/\s+/g, '');
const d = destination.toLowerCase().trim().replace(/\s+/g, '');
return ROUTE_DB[`${o}_${d}`] || ROUTE_DB[`${d}_${o}`] || null;
}
function getStateFromCity(city) {
const map = { mumbai:'MH',pune:'MH',nagpur:'MH',nashik:'MH',delhi:'DL',jaipur:'RJ',lucknow:'UP',bangalore:'KA',chennai:'TN',hyderabad:'TS',kolkata:'WB',ahmedabad:'GJ',surat:'GJ',indore:'MP',kochi:'KL',coimbatore:'TN',vijayawada:'AP',madurai:'TN' };
return map[city.toLowerCase().trim()] || 'MH';
}
const WHATSAPP_TEMPLATES = {
load_available: (d) => `🚛 *लोड उपलब्ध*\n\n📍 ${d.origin}${d.destination}\n💰 ₹${d.budget}\n📦 ${d.truck_type || 'ट्रक'}\n⚖️ ${d.weight || ''} टन\n\n👉 ${d.link}`,
payment_reminder: (d) => `नमस्ते ${d.name || ''} जी,\n\nभाड़ा भुगतान रिमाइंडर:\n📍 ${d.origin}${d.destination}\n💰 बकाया: ₹${d.amount}\n\nकृपया भुगतान करें। धन्यवाद! 🙏`,
safety_checkin: (d) => `✅ *सुरक्षा अपडेट*\n\n${d.message || 'मैं सुरक्षित हूँ।'}\n📍 ${d.location || ''}\n🕐 ${new Date().toLocaleString('en-IN', { timeZone: 'Asia/Kolkata' })}`,
sos: (d) => `🆘 *आपातकालीन अलर्ट*\n\n⚠️ ${d.type || 'Emergency'}\n📍 ${d.location || ''}\n🕐 ${new Date().toLocaleString('en-IN', { timeZone: 'Asia/Kolkata' })}\n\nकृपया तुरंत कॉल करें!`,
};
module.exports = { STATES, DIESEL_PRICES, FREIGHT_CITIES, ROUTE_DB, validateVehicleNumber, formatIndianPhone, generateUPILink, formatINR, estimateToll, calculateTripCost, getRouteInfo, getStateFromCity, WHATSAPP_TEMPLATES };

View file

@ -0,0 +1,27 @@
const path = require('path');
const fs = require('fs');
const LANGS = ['hi', 'en', 'ta', 'te'];
const translations = {};
LANGS.forEach(lang => {
translations[lang] = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'i18n', `${lang}.json`), 'utf8'));
});
function i18n(req, res, next) {
const lang = (req.session && req.session.lang && LANGS.includes(req.session.lang)) ? req.session.lang : 'en';
const strings = translations[lang];
res.locals.lang = lang;
res.locals.t = (key) => {
const parts = key.split('.');
let val = strings;
for (const p of parts) {
val = val && val[p];
}
return val || key;
};
next();
}
module.exports = { i18n, LANGS };

View file

@ -357,3 +357,41 @@ button, input, select, textarea { font-family: inherit; font-size: inherit; }
.bnav-add .bnav-icon { background: var(--saffron); color: #fff; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-top: -12px; font-size: 1rem; } .bnav-add .bnav-icon { background: var(--saffron); color: #fff; width: 36px; height: 36px; border-radius: 50%; display: flex; align-items: center; justify-content: center; margin-top: -12px; font-size: 1rem; }
body { padding-bottom: 70px; } body { padding-bottom: 70px; }
@media (min-width: 768px) { .bottom-nav { display: none; } body { padding-bottom: 0; } } @media (min-width: 768px) { .bottom-nav { display: none; } body { padding-bottom: 0; } }
/* --- Language Switcher --- */
.lang-switcher { display: flex; gap: 4px; margin-right: 12px; }
.lang-btn {
display: inline-flex; align-items: center; justify-content: center;
width: 28px; height: 28px; border-radius: 50%;
background: rgba(255,255,255,0.15); color: #fff;
font-size: 0.7rem; font-weight: 700; text-decoration: none;
border: 2px solid transparent; transition: background 0.2s, border-color 0.2s;
}
.lang-btn:hover { background: rgba(255,255,255,0.3); text-decoration: none; }
.lang-btn.active { border-color: var(--saffron); background: rgba(255,255,255,0.25); }
/* --- Large Icon Nav (low-literacy) --- */
.bnav-icon-lg { font-size: 1.8rem; line-height: 1; }
.bnav-label { font-size: 0.65rem; font-weight: 600; }
/* --- Icon Action Buttons (dashboard) --- */
.icon-action-btn {
display: flex; flex-direction: column; align-items: center; justify-content: center;
gap: 6px; padding: 20px 12px;
background: var(--white); border: 2px solid var(--gray-300);
border-radius: var(--radius-md); text-decoration: none; color: var(--navy);
transition: border-color 0.2s, box-shadow 0.2s;
}
.icon-action-btn:hover { border-color: var(--navy); box-shadow: 0 2px 8px rgba(26,35,126,0.12); text-decoration: none; }
.icon-action-emoji { font-size: 2.5rem; line-height: 1; }
.icon-action-label { font-size: 0.85rem; font-weight: 700; text-align: center; }
/* --- Voice Input Button --- */
.voice-btn {
position: absolute; right: 8px; top: 50%; transform: translateY(-50%);
background: none; border: none; font-size: 1.2rem; cursor: pointer;
padding: 4px; border-radius: 50%; transition: background 0.2s;
}
.voice-btn:hover { background: var(--gray-100); }
.voice-btn.voice-active { animation: pulse 1s infinite; }
@keyframes pulse { 0%,100%{transform:translateY(-50%) scale(1)} 50%{transform:translateY(-50%) scale(1.2)} }

View file

@ -0,0 +1,34 @@
// Voice Input - Web Speech API for low-literacy users
(function() {
if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) return;
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
document.querySelectorAll('.form-input[type="text"], .form-input[type="tel"], .form-input[type="number"]').forEach(input => {
if (input.closest('.no-voice')) return;
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'voice-btn';
btn.innerHTML = '🎤';
btn.title = 'Voice input';
btn.onclick = function(e) {
e.preventDefault();
const recognition = new SpeechRecognition();
recognition.lang = document.documentElement.lang === 'ta' ? 'ta-IN' : document.documentElement.lang === 'te' ? 'te-IN' : document.documentElement.lang === 'en' ? 'en-IN' : 'hi-IN';
recognition.interimResults = false;
btn.classList.add('voice-active');
btn.innerHTML = '🔴';
recognition.start();
recognition.onresult = function(ev) {
const text = ev.results[0][0].transcript;
if (input.type === 'number') input.value = text.replace(/[^\d]/g, '');
else input.value = text;
input.dispatchEvent(new Event('input'));
};
recognition.onend = function() { btn.classList.remove('voice-active'); btn.innerHTML = '🎤'; };
recognition.onerror = function() { btn.classList.remove('voice-active'); btn.innerHTML = '🎤'; };
};
input.parentNode.style.position = 'relative';
input.parentNode.appendChild(btn);
});
})();

View file

@ -91,6 +91,10 @@ router.post('/register', async (req, res) => {
id: user.id, username: user.username, name: user.name, id: user.id, username: user.username, name: user.name,
role: user.role, phone: user.phone, role: user.role, phone: user.phone,
}; };
// Award signup XP
await supabase.from('user_gamification').insert([{ user_id: user.id, xp: 50, login_streak: 1 }]).catch(() => {});
res.redirect('/'); res.redirect('/');
}); });

25
webapp/src/routes/bank.js Normal file
View file

@ -0,0 +1,25 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
router.get('/', requireAuth, async (req, res) => {
const { data: accounts } = await supabase.from('bank_accounts').select('*').eq('user_id', req.session.user.id);
res.render('pages/bank', { accounts: accounts || [] });
});
router.post('/add', requireAuth, async (req, res) => {
const { bank_name, account_number, ifsc, upi_id, account_holder } = req.body;
await supabase.from('bank_accounts').insert([{
user_id: req.session.user.id, bank_name, account_number: account_number || null,
ifsc: ifsc || null, upi_id: upi_id || null, account_holder: account_holder || null,
}]);
res.redirect('/bank');
});
router.post('/delete/:id', requireAuth, async (req, res) => {
await supabase.from('bank_accounts').delete().eq('id', req.params.id).eq('user_id', req.session.user.id);
res.redirect('/bank');
});
module.exports = router;

View file

@ -0,0 +1,42 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
const DAILY_CHALLENGES = [
{ id: 'login', title: 'Open App', title_hi: 'ऐप खोलें', icon: '📱', xp: 10, type: 'easy' },
{ id: 'view_loads', title: 'View 3 Loads', title_hi: '3 लोड देखें', icon: '📋', xp: 15, type: 'easy' },
{ id: 'share_load', title: 'Share a Load', title_hi: 'लोड शेयर करें', icon: '📤', xp: 20, type: 'medium' },
{ id: 'place_bid', title: 'Place a Bid', title_hi: 'बोली लगाएं', icon: '🏷️', xp: 25, type: 'medium' },
{ id: 'safety_checkin', title: 'Safety Check-in', title_hi: 'सुरक्षा चेक-इन', icon: '🛡️', xp: 15, type: 'easy' },
{ id: 'add_ledger', title: 'Log a Trip', title_hi: 'ट्रिप लॉग करें', icon: '📒', xp: 20, type: 'medium' },
{ id: 'invite_friend', title: 'Invite a Friend', title_hi: 'दोस्त को बुलाएं', icon: '🤝', xp: 40, type: 'hard' },
];
router.get('/', requireAuth, async (req, res) => {
const userId = req.session.user.id;
const today = new Date().toISOString().split('T')[0];
const { data: progress } = await supabase.from('challenge_progress').select('challenge_id').eq('user_id', userId).eq('completed_date', today);
const completed = (progress || []).map(p => p.challenge_id);
// Pick 3 daily challenges (rotate by day)
const dayIndex = new Date().getDay();
const todayChallenges = [DAILY_CHALLENGES[dayIndex % 7], DAILY_CHALLENGES[(dayIndex + 2) % 7], DAILY_CHALLENGES[(dayIndex + 4) % 7]];
const challenges = todayChallenges.map(c => ({ ...c, completed: completed.includes(c.id) }));
const streak = completed.length;
res.render('pages/challenges', { challenges, streak, completedCount: completed.length });
});
router.post('/complete', requireAuth, async (req, res) => {
const { challenge_id } = req.body;
const userId = req.session.user.id;
const today = new Date().toISOString().split('T')[0];
await supabase.from('challenge_progress').upsert([{ user_id: userId, challenge_id, completed_date: today }], { onConflict: 'user_id,challenge_id,completed_date' });
const challenge = DAILY_CHALLENGES.find(c => c.id === challenge_id);
if (challenge) {
const { data: gam } = await supabase.from('user_gamification').select('xp').eq('user_id', userId).single();
await supabase.from('user_gamification').upsert([{ user_id: userId, xp: (gam?.xp || 0) + challenge.xp }], { onConflict: 'user_id' });
}
res.redirect('/challenges');
});
module.exports = router;

View file

@ -0,0 +1,33 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
router.get('/', async (req, res) => {
const { category } = req.query;
let query = supabase.from('classifieds').select('*').eq('status', 'active').order('created_at', { ascending: false }).limit(30);
if (category) query = query.eq('category', category);
const { data: listings } = await query;
res.render('pages/classifieds', { listings: listings || [], category: category || 'all' });
});
router.get('/post', requireAuth, (req, res) => {
res.render('pages/classifieds-post');
});
router.post('/post', requireAuth, async (req, res) => {
const { title, description, price, category, location, contact_phone } = req.body;
await supabase.from('classifieds').insert([{
user_id: req.session.user.id, title, description: description || null,
price: parseFloat(price) || 0, category: category || 'truck',
location: location || null, contact_phone: contact_phone || null, status: 'active',
}]);
res.redirect('/classifieds');
});
router.post('/delete/:id', requireAuth, async (req, res) => {
await supabase.from('classifieds').update({ status: 'deleted' }).eq('id', req.params.id).eq('user_id', req.session.user.id);
res.redirect('/classifieds');
});
module.exports = router;

View file

@ -0,0 +1,27 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
router.get('/', requireAuth, async (req, res) => {
const userId = req.session.user.id;
const { data: docs } = await supabase.from('vehicle_documents').select('*').eq('user_id', userId).order('created_at', { ascending: false });
res.render('pages/documents', { documents: docs || [] });
});
router.post('/add', requireAuth, async (req, res) => {
const { doc_type, doc_number, vehicle_number, expiry_date, notes } = req.body;
await supabase.from('vehicle_documents').insert([{
user_id: req.session.user.id, doc_type, doc_number: doc_number || null,
vehicle_number: (vehicle_number || '').toUpperCase().trim(),
expiry_date: expiry_date || null, notes: notes || null, status: 'uploaded',
}]);
res.redirect('/documents');
});
router.post('/delete/:id', requireAuth, async (req, res) => {
await supabase.from('vehicle_documents').delete().eq('id', req.params.id).eq('user_id', req.session.user.id);
res.redirect('/documents');
});
module.exports = router;

View file

@ -0,0 +1,41 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth, requireDriver } = require('../middleware/auth');
// Driver ledger - personal earnings tracker
router.get('/ledger', requireAuth, requireDriver, async (req, res) => {
const userId = req.session.user.id;
const { data: entries } = await supabase.from('driver_ledger').select('*').eq('user_id', userId).order('trip_date', { ascending: false }).limit(50);
const all = entries || [];
const stats = {
total_trips: all.length,
total_earned: all.reduce((s, e) => s + (parseFloat(e.freight_received) || 0), 0),
total_expenses: all.reduce((s, e) => s + (parseFloat(e.fuel_cost) || 0) + (parseFloat(e.toll_cost) || 0) + (parseFloat(e.other_expense) || 0), 0),
};
stats.net_profit = stats.total_earned - stats.total_expenses;
res.render('pages/driver-ledger', { entries: all, stats });
});
// Add trip to ledger
router.get('/ledger/add', requireAuth, requireDriver, (req, res) => {
res.render('pages/driver-ledger-add');
});
router.post('/ledger/add', requireAuth, requireDriver, async (req, res) => {
const { origin, destination, trip_date, freight_received, fuel_cost, toll_cost, other_expense, notes } = req.body;
await supabase.from('driver_ledger').insert([{
user_id: req.session.user.id, origin, destination, trip_date: trip_date || new Date().toISOString().split('T')[0],
freight_received: parseFloat(freight_received) || 0, fuel_cost: parseFloat(fuel_cost) || 0,
toll_cost: parseFloat(toll_cost) || 0, other_expense: parseFloat(other_expense) || 0, notes: notes || null,
}]);
res.redirect('/driver/ledger');
});
// Delete entry
router.post('/ledger/delete/:id', requireAuth, requireDriver, async (req, res) => {
await supabase.from('driver_ledger').delete().eq('id', req.params.id).eq('user_id', req.session.user.id);
res.redirect('/driver/ledger');
});
module.exports = router;

View file

@ -0,0 +1,37 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
const { generateUPILink } = require('../lib/india');
router.get('/', requireAuth, async (req, res) => {
const userId = req.session.user.id;
const { data: fastag } = await supabase.from('fastag_accounts').select('*').eq('user_id', userId).single();
const { data: history } = await supabase.from('toll_history').select('*').eq('user_id', userId).order('created_at', { ascending: false }).limit(30);
const thisMonth = new Date().toISOString().slice(0, 7);
const monthSpend = (history || []).filter(h => h.type === 'toll' && (h.created_at || '').startsWith(thisMonth)).reduce((s, h) => s + (parseFloat(h.amount) || 0), 0);
res.render('pages/fastag', { fastag, history: history || [], stats: { balance: fastag?.balance || 0, month_spend: monthSpend } });
});
router.post('/register', requireAuth, async (req, res) => {
const { fastag_number, vehicle_number, issuer_bank } = req.body;
await supabase.from('fastag_accounts').upsert([{ user_id: req.session.user.id, fastag_number: (fastag_number || '').trim(), vehicle_number: (vehicle_number || '').toUpperCase().trim(), issuer_bank: issuer_bank || null, balance: 0 }], { onConflict: 'user_id' });
res.redirect('/fastag');
});
router.post('/log-toll', requireAuth, async (req, res) => {
const { plaza_name, amount } = req.body;
const userId = req.session.user.id;
await supabase.from('toll_history').insert([{ user_id: userId, type: 'toll', plaza_name, amount: parseFloat(amount) || 0, status: 'completed' }]);
const { data: account } = await supabase.from('fastag_accounts').select('balance').eq('user_id', userId).single();
if (account) await supabase.from('fastag_accounts').update({ balance: Math.max(0, (account.balance || 0) - (parseFloat(amount) || 0)) }).eq('user_id', userId);
res.redirect('/fastag');
});
router.post('/recharge', requireAuth, async (req, res) => {
const { amount } = req.body;
const upi = generateUPILink({ upi_id: process.env.FASTAG_UPI_ID || 'bharathtrucks@upi', amount: parseInt(amount) || 500, note: 'FASTag Recharge' });
res.json({ ...upi, amount: parseInt(amount) || 500 });
});
module.exports = router;

17
webapp/src/routes/feed.js Normal file
View file

@ -0,0 +1,17 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
router.get('/', requireAuth, async (req, res) => {
const { data: events } = await supabase.from('feed_events').select('*').order('created_at', { ascending: false }).limit(30);
res.render('pages/feed', { events: events || [] });
});
// Utility to log feed events (called from other routes)
async function logFeedEvent(type, data) {
await supabase.from('feed_events').insert([{ event_type: type, data, created_at: new Date().toISOString() }]).catch(() => {});
}
module.exports = router;
module.exports.logFeedEvent = logFeedEvent;

View file

@ -0,0 +1,33 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
router.get('/', requireAuth, async (req, res) => {
const userId = req.session.user.id;
const { data: vehicles } = await supabase.from('fleet_vehicles').select('*').eq('owner_id', userId).order('created_at', { ascending: false });
res.render('pages/fleet', { vehicles: vehicles || [] });
});
router.post('/add', requireAuth, async (req, res) => {
const { vehicle_number, vehicle_type, driver_name, driver_phone, capacity_tons } = req.body;
await supabase.from('fleet_vehicles').insert([{
owner_id: req.session.user.id, vehicle_number: (vehicle_number || '').toUpperCase().trim(),
vehicle_type: vehicle_type || 'open', driver_name: driver_name || null,
driver_phone: driver_phone || null, capacity_tons: parseFloat(capacity_tons) || 0, status: 'available',
}]);
res.redirect('/fleet');
});
router.post('/status/:id', requireAuth, async (req, res) => {
const { status } = req.body;
await supabase.from('fleet_vehicles').update({ status }).eq('id', req.params.id).eq('owner_id', req.session.user.id);
res.redirect('/fleet');
});
router.post('/delete/:id', requireAuth, async (req, res) => {
await supabase.from('fleet_vehicles').delete().eq('id', req.params.id).eq('owner_id', req.session.user.id);
res.redirect('/fleet');
});
module.exports = router;

View file

@ -0,0 +1,40 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
const { getLevelForXP, ACHIEVEMENTS, XP_REWARDS } = require('../lib/gamification');
// Profile score / gamification dashboard
router.get('/', requireAuth, async (req, res) => {
const userId = req.session.user.id;
const { data: gam } = await supabase.from('user_gamification').select('*').eq('user_id', userId).single();
const xp = gam?.xp || 0;
const level = getLevelForXP(xp);
const { data: achievements } = await supabase.from('user_achievements').select('achievement_id').eq('user_id', userId);
const earned = (achievements || []).map(a => a.achievement_id);
const allAchievements = ACHIEVEMENTS.map(a => ({ ...a, earned: earned.includes(a.id) }));
res.render('pages/gamification', { level, xp, achievements: allAchievements, streak: gam?.login_streak || 0 });
});
// Onboarding game
router.get('/onboarding', requireAuth, async (req, res) => {
const userId = req.session.user.id;
const { data: gam } = await supabase.from('user_gamification').select('*').eq('user_id', userId).single();
if (!gam) await supabase.from('user_gamification').insert([{ user_id: userId, xp: XP_REWARDS.signup, login_streak: 1 }]);
res.render('pages/onboarding-game', { xp: gam?.xp || XP_REWARDS.signup, steps_completed: gam?.steps_completed || [] });
});
// Award XP (internal API)
router.post('/award', requireAuth, async (req, res) => {
const { action } = req.body;
const userId = req.session.user.id;
const reward = XP_REWARDS[action] || 0;
if (!reward) return res.json({ success: false });
const { data: gam } = await supabase.from('user_gamification').select('xp').eq('user_id', userId).single();
const newXP = (gam?.xp || 0) + reward;
await supabase.from('user_gamification').upsert([{ user_id: userId, xp: newXP }], { onConflict: 'user_id' });
await supabase.from('xp_log').insert([{ user_id: userId, action, xp_earned: reward }]);
res.json({ success: true, xp_earned: reward, total_xp: newXP, level: getLevelForXP(newXP) });
});
module.exports = router;

View file

@ -0,0 +1,38 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
const { generateUPILink } = require('../lib/india');
router.get('/', requireAuth, async (req, res) => {
const userId = req.session.user.id;
const { data: invoices } = await supabase.from('invoices').select('*').eq('user_id', userId).order('created_at', { ascending: false }).limit(20);
res.render('pages/invoices', { invoices: invoices || [] });
});
router.get('/create', requireAuth, (req, res) => {
res.render('pages/invoice-create');
});
router.post('/create', requireAuth, async (req, res) => {
const { client_name, origin, destination, amount, gst_rate, upi_id, notes } = req.body;
const amt = parseFloat(amount) || 0;
const gst = Math.round(amt * ((parseFloat(gst_rate) || 5) / 100));
const total = amt + gst;
const invNo = 'BT-' + Date.now().toString(36).toUpperCase();
const upi = upi_id ? generateUPILink({ upi_id, amount: total, name: client_name, note: `Invoice ${invNo}` }) : null;
await supabase.from('invoices').insert([{
user_id: req.session.user.id, invoice_number: invNo, client_name, origin, destination,
amount: amt, gst_amount: gst, total_amount: total, gst_rate: parseFloat(gst_rate) || 5,
upi_id: upi_id || null, upi_link: upi?.upi_intent || null, notes: notes || null, status: 'unpaid',
}]);
res.redirect('/invoice');
});
router.get('/:id', requireAuth, async (req, res) => {
const { data: invoice } = await supabase.from('invoices').select('*').eq('id', req.params.id).eq('user_id', req.session.user.id).single();
if (!invoice) return res.redirect('/invoice');
res.render('pages/invoice-view', { invoice });
});
module.exports = router;

View file

@ -0,0 +1,18 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
const { getLevelForXP } = require('../lib/gamification');
router.get('/', requireAuth, async (req, res) => {
const { data: top } = await supabase.from('user_gamification').select('user_id, xp, login_streak').order('xp', { ascending: false }).limit(20);
const userIds = (top || []).map(t => t.user_id);
const { data: users } = userIds.length ? await supabase.from('app_users').select('id, name, username, role').in('id', userIds) : { data: [] };
const userMap = {};
(users || []).forEach(u => { userMap[u.id] = u; });
const leaderboard = (top || []).map((t, i) => ({ rank: i + 1, ...t, user: userMap[t.user_id] || {}, level: getLevelForXP(t.xp) }));
const myRank = leaderboard.findIndex(l => l.user_id === req.session.user.id) + 1;
res.render('pages/leaderboard', { leaderboard, myRank });
});
module.exports = router;

View file

@ -58,6 +58,12 @@ router.post('/post', requireAuth, requireRole(ROLES.SHIPPER, ROLES.BROKER), asyn
if (error) { if (error) {
return res.render('pages/post-load', { error: 'लोड पोस्ट करने में त्रुटि', truckTypes: TRUCK_TYPES }); return res.render('pages/post-load', { error: 'लोड पोस्ट करने में त्रुटि', truckTypes: TRUCK_TYPES });
} }
// Award XP for posting load
await supabase.from('user_gamification').upsert([{ user_id: req.session.user.id, xp: 30 }], { onConflict: 'user_id', ignoreDuplicates: false }).catch(() => {});
const { data: gam } = await supabase.from('user_gamification').select('xp').eq('user_id', req.session.user.id).single().catch(() => ({}));
if (gam) await supabase.from('user_gamification').update({ xp: (gam.xp || 0) + 30 }).eq('user_id', req.session.user.id).catch(() => {});
res.redirect('/loadboard'); res.redirect('/loadboard');
}); });
@ -95,6 +101,10 @@ router.post('/:id/bid', requireAuth, requireRole(ROLES.DRIVER), async (req, res)
note: note || null, note: note || null,
}, { onConflict: 'load_id,driver_id' }); }, { onConflict: 'load_id,driver_id' });
// Award XP for placing bid
const { data: gam } = await supabase.from('user_gamification').select('xp').eq('user_id', req.session.user.id).single();
if (gam) await supabase.from('user_gamification').update({ xp: (gam.xp || 0) + 20 }).eq('user_id', req.session.user.id).catch(() => {});
res.redirect(`/loadboard/${req.params.id}`); res.redirect(`/loadboard/${req.params.id}`);
}); });

View file

@ -0,0 +1,36 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
router.get('/', requireAuth, async (req, res) => {
const userId = req.session.user.id;
const { data: reminders } = await supabase.from('vehicle_reminders').select('*').eq('user_id', userId).order('expiry_date', { ascending: true });
const today = new Date();
const enriched = (reminders || []).map(r => {
const daysLeft = Math.ceil((new Date(r.expiry_date) - today) / 86400000);
const urgency = daysLeft < 0 ? 'expired' : daysLeft <= 7 ? 'critical' : daysLeft <= 30 ? 'warning' : 'valid';
return { ...r, days_left: daysLeft, urgency };
});
const stats = { total: enriched.length, expired: enriched.filter(r => r.urgency === 'expired').length, expiring: enriched.filter(r => r.urgency === 'critical' || r.urgency === 'warning').length };
res.render('pages/maintenance', { reminders: enriched, stats });
});
router.post('/add', requireAuth, async (req, res) => {
const { vehicle_number, doc_type, expiry_date, notes } = req.body;
await supabase.from('vehicle_reminders').insert([{ user_id: req.session.user.id, vehicle_number: (vehicle_number || '').toUpperCase().trim(), doc_type: doc_type || 'insurance', expiry_date, notes: notes || null, status: 'active' }]);
res.redirect('/maintenance');
});
router.post('/delete/:id', requireAuth, async (req, res) => {
await supabase.from('vehicle_reminders').delete().eq('id', req.params.id).eq('user_id', req.session.user.id);
res.redirect('/maintenance');
});
router.post('/renew/:id', requireAuth, async (req, res) => {
const { new_expiry_date } = req.body;
await supabase.from('vehicle_reminders').update({ expiry_date: new_expiry_date }).eq('id', req.params.id).eq('user_id', req.session.user.id);
res.redirect('/maintenance');
});
module.exports = router;

View file

@ -0,0 +1,52 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
const { ROUTE_DB, FREIGHT_CITIES } = require('../lib/india');
router.get('/', requireAuth, (req, res) => {
res.render('pages/games-hub');
});
// Rate Guesser - guess the freight rate for a route
router.get('/rate-guesser', requireAuth, async (req, res) => {
const { data: loads } = await supabase.from('loads').select('origin_city, destination_city, budget, weight_tons').not('budget', 'is', null).gt('budget', 0).limit(50);
const pool = (loads || []).filter(l => l.budget > 5000);
const load = pool.length > 0 ? pool[Math.floor(Math.random() * pool.length)] : { origin_city: 'Mumbai', destination_city: 'Delhi', budget: 45000, weight_tons: 15 };
res.render('pages/games-rate-guesser', { load, revealed: false });
});
router.post('/rate-guesser/guess', requireAuth, async (req, res) => {
const { guess, actual, origin, destination, weight } = req.body;
const g = parseInt(guess) || 0;
const a = parseInt(actual) || 0;
const diff = Math.abs(g - a);
const accuracy = a > 0 ? Math.max(0, 100 - Math.round((diff / a) * 100)) : 0;
let xpEarned = accuracy >= 90 ? 25 : accuracy >= 70 ? 15 : accuracy >= 50 ? 10 : 5;
const { data: gam } = await supabase.from('user_gamification').select('xp').eq('user_id', req.session.user.id).single();
if (gam) await supabase.from('user_gamification').update({ xp: (gam.xp || 0) + xpEarned }).eq('user_id', req.session.user.id);
res.render('pages/games-rate-guesser', { load: { origin_city: origin, destination_city: destination, budget: a, weight_tons: weight }, revealed: true, guess: g, accuracy, xpEarned });
});
// Route Quiz - guess distance between cities
router.get('/route-quiz', requireAuth, (req, res) => {
const keys = Object.keys(ROUTE_DB);
const key = keys[Math.floor(Math.random() * keys.length)];
const [o, d] = key.split('_');
const route = ROUTE_DB[key];
res.render('pages/games-route-quiz', { origin: o.charAt(0).toUpperCase() + o.slice(1), destination: d.charAt(0).toUpperCase() + d.slice(1), actual_km: route.km, revealed: false });
});
router.post('/route-quiz/guess', requireAuth, async (req, res) => {
const { guess, actual_km, origin, destination } = req.body;
const g = parseInt(guess) || 0;
const a = parseInt(actual_km) || 0;
const diff = Math.abs(g - a);
const accuracy = a > 0 ? Math.max(0, 100 - Math.round((diff / a) * 100)) : 0;
let xpEarned = accuracy >= 90 ? 20 : accuracy >= 70 ? 12 : accuracy >= 50 ? 8 : 3;
const { data: gam } = await supabase.from('user_gamification').select('xp').eq('user_id', req.session.user.id).single();
if (gam) await supabase.from('user_gamification').update({ xp: (gam.xp || 0) + xpEarned }).eq('user_id', req.session.user.id);
res.render('pages/games-route-quiz', { origin, destination, actual_km: a, revealed: true, guess: g, accuracy, xpEarned });
});
module.exports = router;

19
webapp/src/routes/news.js Normal file
View file

@ -0,0 +1,19 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
router.get('/', requireAuth, async (req, res) => {
const { data: news } = await supabase.from('trucker_news').select('*').eq('status', 'published').order('created_at', { ascending: false }).limit(20);
res.render('pages/news', { news: news || [] });
});
// Admin post news
router.post('/post', requireAuth, async (req, res) => {
if (req.session.user.role !== 'admin') return res.redirect('/news');
const { title, content, category } = req.body;
await supabase.from('trucker_news').insert([{ title, content, category: category || 'general', status: 'published', posted_by: req.session.user.id }]);
res.redirect('/news');
});
module.exports = router;

View file

@ -0,0 +1,37 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
router.get('/', requireAuth, async (req, res) => {
const userId = req.session.user.id;
const notifications = [];
const today = new Date();
// Overdue payments (for shippers/brokers)
if (req.session.user.role === 'shipper' || req.session.user.role === 'broker') {
const { data: trips } = await supabase.from('trips').select('*, load:load_id(origin_city, destination_city, budget)').eq('shipper_id', userId).eq('status', 'delivered');
(trips || []).forEach(t => {
const days = Math.floor((today - new Date(t.created_at)) / 86400000);
if (days >= 3) notifications.push({ type: 'payment', icon: '💰', title: `Payment pending ${days} days`, subtitle: `${t.load?.origin_city}${t.load?.destination_city} • ₹${t.amount}`, priority: days >= 7 ? 'high' : 'medium', url: `/trips` });
});
}
// Bid updates (for drivers)
if (req.session.user.role === 'driver') {
const { data: bids } = await supabase.from('bids').select('*, load:load_id(origin_city, destination_city, status)').eq('driver_id', userId).eq('status', 'accepted').limit(5);
(bids || []).forEach(b => { notifications.push({ type: 'bid', icon: '✅', title: `Bid accepted!`, subtitle: `${b.load?.origin_city}${b.load?.destination_city}`, priority: 'high', url: `/loadboard/${b.load_id}` }); });
}
// Maintenance reminders
const { data: reminders } = await supabase.from('vehicle_reminders').select('*').eq('user_id', userId).eq('status', 'active').lte('expiry_date', new Date(Date.now() + 15 * 86400000).toISOString().split('T')[0]);
(reminders || []).forEach(r => {
const days = Math.ceil((new Date(r.expiry_date) - today) / 86400000);
notifications.push({ type: 'maintenance', icon: days < 0 ? '🔴' : '🟡', title: `${r.doc_type} ${days < 0 ? 'expired' : 'expiring in ' + days + ' days'}`, subtitle: r.vehicle_number, priority: days < 0 ? 'high' : 'medium', url: '/maintenance' });
});
notifications.sort((a, b) => ({ high: 0, medium: 1, low: 2 }[a.priority] || 2) - ({ high: 0, medium: 1, low: 2 }[b.priority] || 2));
res.render('pages/notifications', { notifications });
});
module.exports = router;

View file

@ -0,0 +1,21 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
router.get('/', requireAuth, async (req, res) => {
const { origin, destination } = req.query;
let rates = null;
if (origin && destination) {
const { data: loads } = await supabase.from('loads').select('budget, weight_tons, created_at')
.ilike('origin_city', `%${origin}%`).ilike('destination_city', `%${destination}%`)
.not('budget', 'is', null).order('created_at', { ascending: false }).limit(20);
if (loads && loads.length > 0) {
const budgets = loads.map(l => parseFloat(l.budget)).filter(b => b > 0);
rates = { origin, destination, count: budgets.length, avg: Math.round(budgets.reduce((a, b) => a + b, 0) / budgets.length), min: Math.min(...budgets), max: Math.max(...budgets), per_ton_avg: Math.round(budgets.reduce((a, b) => a + b, 0) / budgets.length / (loads[0].weight_tons || 1)) };
}
}
res.render('pages/rates', { rates, origin: origin || '', destination: destination || '' });
});
module.exports = router;

View file

@ -0,0 +1,25 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
router.get('/', requireAuth, async (req, res) => {
const userId = req.session.user.id;
const code = req.session.user.username || userId.slice(0, 8);
const { data: referrals } = await supabase.from('referrals').select('*').eq('referrer_id', userId).order('created_at', { ascending: false });
const stats = { total: (referrals || []).length, joined: (referrals || []).filter(r => r.status === 'joined').length };
const shareMsg = `🚛 भारत ट्रक्स पर आओ! मुफ्त लोड बोर्ड, ट्रिप ट्रैकर, FASTag। मेरा कोड: ${code}\n👉 https://bharathtrucks.com/register?ref=${code}`;
res.render('pages/referral', { code, referrals: referrals || [], stats, shareMsg });
});
router.post('/track', async (req, res) => {
const { referrer_code, new_user_id } = req.body;
if (!referrer_code || !new_user_id) return res.json({ success: false });
const { data: referrer } = await supabase.from('app_users').select('id').or(`username.eq.${referrer_code},id.ilike.${referrer_code}%`).single();
if (referrer) {
await supabase.from('referrals').insert([{ referrer_id: referrer.id, referred_user_id: new_user_id, referral_code: referrer_code, status: 'joined' }]);
}
res.json({ success: true });
});
module.exports = router;

View file

@ -0,0 +1,33 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
router.get('/', requireAuth, async (req, res) => {
const userId = req.session.user.id;
const thisMonth = new Date().toISOString().slice(0, 7);
const { data: trips } = await supabase.from('trips').select('amount, status, created_at').or(`driver_id.eq.${userId},shipper_id.eq.${userId}`);
const { data: ledger } = await supabase.from('driver_ledger').select('freight_received, fuel_cost, toll_cost, other_expense, trip_date').eq('user_id', userId);
const allTrips = trips || [];
const allLedger = ledger || [];
const monthTrips = allTrips.filter(t => (t.created_at || '').startsWith(thisMonth));
const monthLedger = allLedger.filter(l => (l.trip_date || '').startsWith(thisMonth));
const stats = {
total_trips: allTrips.length, month_trips: monthTrips.length,
total_revenue: allLedger.reduce((s, l) => s + (parseFloat(l.freight_received) || 0), 0),
month_revenue: monthLedger.reduce((s, l) => s + (parseFloat(l.freight_received) || 0), 0),
total_expenses: allLedger.reduce((s, l) => s + (parseFloat(l.fuel_cost) || 0) + (parseFloat(l.toll_cost) || 0) + (parseFloat(l.other_expense) || 0), 0),
};
stats.profit = stats.total_revenue - stats.total_expenses;
res.render('pages/reports', { stats, ledger: allLedger });
});
router.get('/export', requireAuth, async (req, res) => {
const userId = req.session.user.id;
const { data: ledger } = await supabase.from('driver_ledger').select('*').eq('user_id', userId).order('trip_date', { ascending: false });
let csv = 'Date,From,To,Freight,Fuel,Toll,Other,Notes\n';
(ledger || []).forEach(l => { csv += `${l.trip_date},${l.origin},${l.destination},${l.freight_received},${l.fuel_cost},${l.toll_cost},${l.other_expense},${(l.notes||'').replace(/,/g,' ')}\n`; });
res.set({ 'Content-Type': 'text/csv', 'Content-Disposition': 'attachment; filename=bharathtrucks-report.csv' }).send(csv);
});
module.exports = router;

View file

@ -0,0 +1,30 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth, requireDriver } = require('../middleware/auth');
// View return load page
router.get('/', requireAuth, requireDriver, async (req, res) => {
const userId = req.session.user.id;
const { data: availability } = await supabase.from('available_for_return').select('*').eq('user_id', userId).single();
const { data: suggestions } = availability ? await supabase.from('loads').select('*').eq('status', 'open').ilike('origin_city', `%${availability.current_city}%`).order('created_at', { ascending: false }).limit(10) : { data: [] };
res.render('pages/return-load', { availability, suggestions: suggestions || [] });
});
// Mark available for return load
router.post('/available', requireAuth, requireDriver, async (req, res) => {
const { current_city, home_city, vehicle_type } = req.body;
await supabase.from('available_for_return').upsert([{
user_id: req.session.user.id, current_city, home_city: home_city || null,
vehicle_type: vehicle_type || null, status: 'looking', updated_at: new Date().toISOString(),
}], { onConflict: 'user_id' });
res.redirect('/returnload');
});
// Cancel availability
router.post('/cancel', requireAuth, requireDriver, async (req, res) => {
await supabase.from('available_for_return').update({ status: 'inactive' }).eq('user_id', req.session.user.id);
res.redirect('/returnload');
});
module.exports = router;

View file

@ -0,0 +1,49 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth, requireDriver } = require('../middleware/auth');
const { WHATSAPP_TEMPLATES } = require('../lib/india');
router.get('/', requireAuth, requireDriver, async (req, res) => {
const userId = req.session.user.id;
const { data: contacts } = await supabase.from('safety_contacts').select('*').eq('user_id', userId);
const { data: checkins } = await supabase.from('safety_checkins').select('*').eq('user_id', userId).order('created_at', { ascending: false }).limit(10);
res.render('pages/safety', { contacts: contacts || [], checkins: checkins || [] });
});
// Add emergency contact
router.post('/contacts/add', requireAuth, requireDriver, async (req, res) => {
const { contact_name, contact_phone, relationship } = req.body;
await supabase.from('safety_contacts').insert([{ user_id: req.session.user.id, contact_name, contact_phone: (contact_phone || '').replace(/\s/g, ''), relationship: relationship || 'family' }]);
res.redirect('/safety');
});
// Delete contact
router.post('/contacts/delete/:id', requireAuth, async (req, res) => {
await supabase.from('safety_contacts').delete().eq('id', req.params.id).eq('user_id', req.session.user.id);
res.redirect('/safety');
});
// Check-in (generates WhatsApp links)
router.post('/checkin', requireAuth, requireDriver, async (req, res) => {
const { location, message } = req.body;
const userId = req.session.user.id;
const { data: contacts } = await supabase.from('safety_contacts').select('*').eq('user_id', userId);
const msg = WHATSAPP_TEMPLATES.safety_checkin({ message: message || 'मैं सुरक्षित हूँ', location });
await supabase.from('safety_checkins').insert([{ user_id: userId, location, message: message || 'Safe', is_sos: false }]);
const links = (contacts || []).map(c => ({ name: c.contact_name, url: `https://wa.me/${c.contact_phone.replace(/\D/g, '')}?text=${encodeURIComponent(msg)}` }));
res.render('pages/safety-sent', { links, message: msg, is_sos: false });
});
// SOS Emergency
router.post('/sos', requireAuth, requireDriver, async (req, res) => {
const { location, emergency_type } = req.body;
const userId = req.session.user.id;
const { data: contacts } = await supabase.from('safety_contacts').select('*').eq('user_id', userId);
const msg = WHATSAPP_TEMPLATES.sos({ type: emergency_type || 'Emergency', location });
await supabase.from('safety_checkins').insert([{ user_id: userId, location, message: `SOS: ${emergency_type}`, is_sos: true }]);
const links = (contacts || []).map(c => ({ name: c.contact_name, url: `https://wa.me/${c.contact_phone.replace(/\D/g, '')}?text=${encodeURIComponent(msg)}`, call: `tel:${c.contact_phone}` }));
res.render('pages/safety-sent', { links, message: msg, is_sos: true });
});
module.exports = router;

View file

@ -0,0 +1,19 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { requireAuth } = require('../middleware/auth');
router.get('/', requireAuth, async (req, res) => {
const { q } = req.query;
let results = { loads: [], users: [], classifieds: [] };
if (q && q.length >= 2) {
const term = `%${q}%`;
const { data: loads } = await supabase.from('loads').select('id, origin_city, destination_city, budget, status').or(`origin_city.ilike.${term},destination_city.ilike.${term}`).limit(10);
const { data: users } = await supabase.from('app_users').select('id, name, username, role').or(`name.ilike.${term},username.ilike.${term}`).limit(10);
const { data: classifieds } = await supabase.from('classifieds').select('id, title, price, category').ilike('title', term).eq('status', 'active').limit(10);
results = { loads: loads || [], users: users || [], classifieds: classifieds || [] };
}
res.render('pages/search', { q: q || '', results });
});
module.exports = router;

View file

@ -0,0 +1,21 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
router.get('/sitemap.xml', async (req, res) => {
const base = `${req.protocol}://${req.get('host')}`;
const { data: loads } = await supabase.from('loads').select('id, created_at').eq('status', 'open').order('created_at', { ascending: false }).limit(200);
let xml = '<?xml version="1.0" encoding="UTF-8"?><urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">';
xml += `<url><loc>${base}/</loc><priority>1.0</priority></url>`;
xml += `<url><loc>${base}/loadboard</loc><changefreq>hourly</changefreq><priority>0.9</priority></url>`;
xml += `<url><loc>${base}/register</loc><priority>0.8</priority></url>`;
(loads || []).forEach(l => { xml += `<url><loc>${base}/loadboard/share/${l.id}</loc><lastmod>${l.created_at?.split('T')[0]}</lastmod></url>`; });
xml += '</urlset>';
res.set('Content-Type', 'application/xml').send(xml);
});
router.get('/robots.txt', (req, res) => {
res.type('text/plain').send(`User-agent: *\nAllow: /\nSitemap: ${req.protocol}://${req.get('host')}/sitemap.xml`);
});
module.exports = router;

View file

@ -0,0 +1,27 @@
const express = require('express');
const router = express.Router();
const { calculateTripCost, getRouteInfo, getStateFromCity, FREIGHT_CITIES } = require('../lib/india');
router.get('/', (req, res) => {
res.render('pages/trip-planner', { result: null, cities: FREIGHT_CITIES });
});
router.post('/calculate', (req, res) => {
const { origin, destination, vehicle_type, mileage, freight_charged } = req.body;
if (!origin || !destination) return res.render('pages/trip-planner', { result: { error: 'Origin and destination required' }, cities: FREIGHT_CITIES });
const route = getRouteInfo(origin, destination);
if (!route) return res.render('pages/trip-planner', { result: { error: 'Route not in database. Try major cities.' }, cities: FREIGHT_CITIES });
const result = calculateTripCost({
distance_km: route.km, vehicle_type: vehicle_type || 'truck',
origin_state: getStateFromCity(origin), dest_state: getStateFromCity(destination),
freight_charged: parseFloat(freight_charged) || 0, mileage: parseFloat(mileage) || 4,
});
result.origin = origin; result.destination = destination;
result.distance_km = route.km; result.hours = route.hours; result.toll_plazas = route.tolls;
res.render('pages/trip-planner', { result, cities: FREIGHT_CITIES });
});
module.exports = router;

View file

@ -33,6 +33,12 @@ router.post('/:id/status', async (req, res) => {
if (trip) await supabase.from('loads').update({ status }).eq('id', trip.load_id); if (trip) await supabase.from('loads').update({ status }).eq('id', trip.load_id);
} }
// Award XP on delivery
if (status === 'delivered') {
const { data: gam } = await supabase.from('user_gamification').select('xp').eq('user_id', req.session.user.id).single();
if (gam) await supabase.from('user_gamification').update({ xp: (gam.xp || 0) + 40 }).eq('user_id', req.session.user.id).catch(() => {});
}
res.redirect('/trips'); res.redirect('/trips');
}); });

View file

@ -0,0 +1,22 @@
const express = require('express');
const router = express.Router();
const supabase = require('../services/supabase');
const { WHATSAPP_TEMPLATES } = require('../lib/india');
// Public share page with OG tags for WhatsApp preview
router.get('/share/:id', async (req, res) => {
const { data: load } = await supabase.from('loads').select('*').eq('id', req.params.id).single();
if (!load) return res.status(404).render('pages/404');
res.render('pages/load-share', { load, layout: false });
});
// Generate WhatsApp share link
router.get('/whatsapp/:id', async (req, res) => {
const { data: load } = await supabase.from('loads').select('*').eq('id', req.params.id).single();
if (!load) return res.status(404).json({ error: 'Not found' });
const link = `${req.protocol}://${req.get('host')}/loadboard/share/${load.id}`;
const msg = WHATSAPP_TEMPLATES.load_available({ origin: load.origin_city, destination: load.destination_city, budget: load.budget, truck_type: load.truck_type, weight: load.weight_tons, link });
res.redirect(`https://wa.me/?text=${encodeURIComponent(msg)}`);
});
module.exports = router;

View file

@ -38,8 +38,8 @@ app.set('views', path.join(__dirname, 'views'));
// Session // Session
app.use(session({ app.use(session({
secret: config.session.secret, secret: config.session.secret,
resave: false, resave: true,
saveUninitialized: false, saveUninitialized: true,
cookie: { secure: false, maxAge: 24 * 60 * 60 * 1000 }, cookie: { secure: false, maxAge: 24 * 60 * 60 * 1000 },
})); }));
@ -48,25 +48,95 @@ app.use((req, res, next) => {
res.locals.user = req.session.user || null; res.locals.user = req.session.user || null;
res.locals.appName = 'भारत ट्रक्स'; res.locals.appName = 'भारत ट्रक्स';
res.locals.appNameEn = 'BharathTrucks'; res.locals.appNameEn = 'BharathTrucks';
res.locals.formatINR = require('./lib/india').formatINR;
next(); next();
}); });
// i18n
const { i18n, LANGS } = require('./middleware/i18n');
app.use(i18n);
app.get('/lang/:code', (req, res) => {
const code = req.params.code;
if (LANGS.includes(code)) req.session.lang = code;
req.session.save(() => {
res.redirect(req.get('Referer') || '/');
});
});
// Routes // Routes
const authRoutes = require('./routes/auth'); const authRoutes = require('./routes/auth');
const loadRoutes = require('./routes/loads'); const loadRoutes = require('./routes/loads');
const tripRoutes = require('./routes/trips'); const tripRoutes = require('./routes/trips');
const adminRoutes = require('./routes/admin'); const adminRoutes = require('./routes/admin');
const messageRoutes = require('./routes/messages'); const messageRoutes = require('./routes/messages');
// Phase 1 routes
const whatsappRoutes = require('./routes/whatsapp');
const driverLedgerRoutes = require('./routes/driver-ledger');
const tripplannerRoutes = require('./routes/tripplanner');
const returnloadRoutes = require('./routes/returnload');
const safetyRoutes = require('./routes/safety');
const maintenanceRoutes = require('./routes/maintenance');
const fastagRoutes = require('./routes/fastag');
const notificationsRoutes = require('./routes/notifications');
// Phase 2 routes
const gamificationRoutes = require('./routes/gamification');
const referralRoutes = require('./routes/referral');
const feedRoutes = require('./routes/feed');
const leaderboardRoutes = require('./routes/leaderboard');
const challengesRoutes = require('./routes/challenges');
const invoiceRoutes = require('./routes/invoice');
const ratesRoutes = require('./routes/rates');
const sitemapRoutes = require('./routes/sitemap');
app.use('/', authRoutes); app.use('/', authRoutes);
app.use('/loadboard', loadRoutes); app.use('/loadboard', loadRoutes);
app.use('/loadboard', whatsappRoutes);
app.use('/trips', tripRoutes); app.use('/trips', tripRoutes);
app.use('/admin', adminRoutes); app.use('/admin', adminRoutes);
app.use('/messages', messageRoutes); app.use('/messages', messageRoutes);
app.use('/driver', driverLedgerRoutes);
app.use('/trip-planner', tripplannerRoutes);
app.use('/returnload', returnloadRoutes);
app.use('/safety', safetyRoutes);
app.use('/maintenance', maintenanceRoutes);
app.use('/fastag', fastagRoutes);
app.use('/notifications', notificationsRoutes);
// Phase 2
app.use('/gamification', gamificationRoutes);
app.use('/referral', referralRoutes);
app.use('/feed', feedRoutes);
app.use('/leaderboard', leaderboardRoutes);
app.use('/challenges', challengesRoutes);
app.use('/invoice', invoiceRoutes);
app.use('/rates', ratesRoutes);
app.use('/', sitemapRoutes);
// Phase 3
const minigamesRoutes = require('./routes/minigames');
const fleetRoutes = require('./routes/fleet');
const classifiedsRoutes = require('./routes/classifieds');
const documentsRoutes = require('./routes/documents');
const bankRoutes = require('./routes/bank');
const searchRoutes = require('./routes/search');
const reportsRoutes = require('./routes/reports');
const newsRoutes = require('./routes/news');
app.use('/games', minigamesRoutes);
app.use('/fleet', fleetRoutes);
app.use('/classifieds', classifiedsRoutes);
app.use('/documents', documentsRoutes);
app.use('/bank', bankRoutes);
app.use('/search', searchRoutes);
app.use('/reports', reportsRoutes);
app.use('/news', newsRoutes);
const { requireAuth, requireDriver, requireShipper, requireBroker } = require('./middleware/auth'); const { requireAuth, requireDriver, requireShipper, requireBroker } = require('./middleware/auth');
const supabase = require('./services/supabase'); const supabase = require('./services/supabase');
app.get('/health', (req, res) => res.json({ status: 'ok', ts: Date.now() })); app.get('/health', (req, res) => res.json({ status: 'ok', ts: Date.now() }));
app.get('/more', requireAuth, (req, res) => res.render('pages/more'));
app.get('/', (req, res) => { app.get('/', (req, res) => {
if (req.session && req.session.user) { if (req.session && req.session.user) {
const { ROLES } = require('./config/constants'); const { ROLES } = require('./config/constants');

View file

@ -1,9 +1,6 @@
<% var title = '404 - पृष्ठ नहीं मिला'; %> <% var title = '404'; %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <section class="section text-center" style="padding-top:60px">
<div class="error-page"> <div class="container"><h1 style="font-size:3rem;color:var(--navy)">404</h1><p style="color:var(--gray-700);margin:var(--space-md) 0">Page not found</p><a href="/" class="btn btn-primary">🏠 Home</a></div>
<h1>404</h1> </section>
<p>यह पृष्ठ उपलब्ध नहीं है। | Page not found.</p>
<a href="/" class="btn btn-primary">मुख्य पृष्ठ पर जाएं</a>
</div>
<%- include('../partials/footer') %> <%- include('../partials/footer') %>

View file

@ -1,9 +1,6 @@
<% var title = '500 - सर्वर त्रुटि'; %> <% var title = 'Error'; %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <section class="section text-center" style="padding-top:60px">
<div class="error-page"> <div class="container"><h1 style="font-size:3rem;color:var(--navy)">500</h1><p style="color:var(--gray-700);margin:var(--space-md) 0">Something went wrong</p><a href="/" class="btn btn-primary">🏠 Home</a></div>
<h1>500</h1> </section>
<p>कुछ गलत हो गया। कृपया बाद में पुनः प्रयास करें। | Something went wrong.</p>
<a href="/" class="btn btn-primary">मुख्य पृष्ठ पर जाएं</a>
</div>
<%- include('../partials/footer') %> <%- include('../partials/footer') %>

View file

@ -1,46 +1,21 @@
<% var title = 'एडमिन पैनल'; %> <% var title = 'Admin Dashboard'; %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)"> <section class="section" style="padding-top:var(--space-lg)">
<div class="container"> <div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🏛️ एडमिन पैनल</h2> <h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🏛️ Admin Dashboard</h2>
<div class="stats-grid" style="margin-bottom:var(--space-lg)">
<div class="stats-grid"> <div class="stat-card"><div class="stat-value"><%= stats.totalUsers %></div><div class="stat-label">👤 Users</div></div>
<div class="stat-card"><div class="stat-value"><%= stats.users %></div><div class="stat-label">कुल उपयोगकर्ता</div></div> <div class="stat-card"><div class="stat-value"><%= stats.totalLoads %></div><div class="stat-label">📦 Loads</div></div>
<div class="stat-card"><div class="stat-value"><%= stats.loads %></div><div class="stat-label">कुल लोड</div></div> <div class="stat-card"><div class="stat-value"><%= stats.totalTrips %></div><div class="stat-label">🚛 Trips</div></div>
<div class="stat-card"><div class="stat-value"><%= stats.bids %></div><div class="stat-label">कुल बोलियाँ</div></div> <div class="stat-card"><div class="stat-value"><%= stats.totalBids %></div><div class="stat-label">🏷️ Bids</div></div>
<div class="stat-card"><div class="stat-value"><%= stats.trips %></div><div class="stat-label">कुल ट्रिप</div></div>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)">
<div class="stats-grid" style="margin-top:var(--space-md)"> <a href="/admin/users" class="icon-action-btn"><span class="icon-action-emoji">👥</span><span class="icon-action-label">Users</span></a>
<div class="stat-card"><div class="stat-value"><%= roles.driver %></div><div class="stat-label">🚛 ड्राइवर</div></div> <a href="/admin/loads" class="icon-action-btn"><span class="icon-action-emoji">📦</span><span class="icon-action-label">Loads</span></a>
<div class="stat-card"><div class="stat-value"><%= roles.shipper %></div><div class="stat-label">📦 शिपर</div></div> <a href="/news" class="icon-action-btn"><span class="icon-action-emoji">📰</span><span class="icon-action-label">News</span></a>
<div class="stat-card"><div class="stat-value"><%= roles.broker %></div><div class="stat-label">🤝 ब्रोकर</div></div> <a href="/reports" class="icon-action-btn"><span class="icon-action-emoji">📊</span><span class="icon-action-label">Reports</span></a>
</div>
<% if (recentUsers.length > 0) { %>
<h3 style="font-size:1rem;margin-top:var(--space-lg);margin-bottom:var(--space-sm)">नए उपयोगकर्ता</h3>
<div class="card" style="padding:0;overflow:hidden">
<table style="width:100%;border-collapse:collapse;font-size:0.8rem">
<tr style="background:var(--gray-100)"><th style="padding:8px;text-align:left">नाम</th><th>यूज़रनेम</th><th>भूमिका</th><th>तारीख</th></tr>
<% recentUsers.forEach(u => { %>
<tr style="border-top:1px solid var(--gray-200)">
<td style="padding:8px"><%= u.name %></td>
<td style="padding:8px"><%= u.username %></td>
<td style="padding:8px"><span class="badge badge-open"><%= u.role %></span></td>
<td style="padding:8px"><%= new Date(u.created_at).toLocaleDateString('hi-IN') %></td>
</tr>
<% }) %>
</table>
</div>
<% } %>
<div style="margin-top:var(--space-lg);display:grid;grid-template-columns:1fr 1fr;gap:var(--space-sm)">
<a href="/admin/users" class="btn btn-primary btn-block">👥 उपयोगकर्ता</a>
<a href="/admin/loads" class="btn btn-outline btn-block">📋 लोड</a>
</div> </div>
</div> </div>
</section> </section>
<%- include('../partials/footer') %> <%- include('../partials/footer') %>

View file

@ -1,30 +1,17 @@
<% var title = 'सभी लोड — एडमिन'; %> <% var title = 'Admin - Loads'; %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)"> <section class="section" style="padding-top:var(--space-lg)">
<div class="container"> <div class="container">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-md)"> <h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">📦 All Loads (<%= loads.length %>)</h2>
<h2 style="font-size:1.3rem">📋 सभी लोड (<%= loads.length %>)</h2> <% loads.forEach(l => { %>
<a href="/admin" style="font-size:0.8rem">← एडमिन</a> <a href="/loadboard/<%= l.id %>" class="card card-accent" style="display:block;text-decoration:none;color:inherit;margin-bottom:var(--space-sm)">
</div> <div style="display:flex;justify-content:space-between;align-items:center">
<div><strong><%= l.origin_city %> → <%= l.destination_city %></strong><div style="font-size:0.8rem;color:var(--gray-700)"><%= l.weight_tons %> tons | <%= l.truck_type %></div></div>
<div class="card" style="padding:0;overflow-x:auto"> <span class="badge badge-<%= l.status==='open'?'open':l.status==='booked'?'booked':'delivered' %>"><%= l.status %></span>
<table style="width:100%;border-collapse:collapse;font-size:0.8rem;min-width:600px"> </div>
<tr style="background:var(--gray-100)"><th style="padding:8px;text-align:left">रूट</th><th>वज़न</th><th>बजट</th><th>बोली</th><th>स्थिति</th><th>पोस्टर</th></tr> </a>
<% loads.forEach(l => { %> <% }) %>
<tr style="border-top:1px solid var(--gray-200)">
<td style="padding:8px"><a href="/loadboard/<%= l.id %>"><%= l.origin_city %> → <%= l.destination_city %></a></td>
<td style="padding:8px"><%= l.weight_tons %>T</td>
<td style="padding:8px"><%= l.budget ? '₹' + Number(l.budget).toLocaleString('en-IN') : '-' %></td>
<td style="padding:8px"><%= l.bid_count %></td>
<td style="padding:8px"><span class="badge badge-<%= l.status === 'open' ? 'open' : l.status === 'booked' ? 'booked' : 'delivered' %>"><%= l.status %></span></td>
<td style="padding:8px"><%= l.poster ? l.poster.name : '-' %></td>
</tr>
<% }) %>
</table>
</div>
</div> </div>
</section> </section>
<%- include('../partials/footer') %> <%- include('../partials/footer') %>

View file

@ -1,44 +1,15 @@
<% var title = 'उपयोगकर्ता प्रबंधन'; %> <% var title = 'Admin - Users'; %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)"> <section class="section" style="padding-top:var(--space-lg)">
<div class="container"> <div class="container">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-md)"> <h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">👥 All Users (<%= users.length %>)</h2>
<h2 style="font-size:1.3rem">👥 उपयोगकर्ता (<%= users.length %>)</h2> <% users.forEach(u => { %>
<a href="/admin" style="font-size:0.8rem">← एडमिन</a> <div class="card" style="padding:12px;margin-bottom:8px;display:flex;justify-content:space-between;align-items:center">
</div> <div><strong><%= u.name || u.username %></strong><div style="font-size:0.75rem;color:var(--gray-700)">@<%= u.username %> | <%= u.phone || '' %></div></div>
<span class="badge badge-<%= u.role==='driver'?'transit':u.role==='shipper'?'open':'booked' %>"><%= u.role %></span>
<form method="GET" action="/admin/users" style="display:flex;gap:var(--space-sm);margin-bottom:var(--space-md)">
<input type="text" name="search" class="form-input" placeholder="नाम या यूज़रनेम खोजें" value="<%= filters.search || '' %>" style="padding:8px 12px">
<select name="role" class="form-input form-select" style="width:auto;padding:8px 12px">
<option value="all">सभी</option>
<option value="driver" <%= filters.role === 'driver' ? 'selected' : '' %>>ड्राइवर</option>
<option value="shipper" <%= filters.role === 'shipper' ? 'selected' : '' %>>शिपर</option>
<option value="broker" <%= filters.role === 'broker' ? 'selected' : '' %>>ब्रोकर</option>
</select>
<button class="btn btn-primary btn-sm">खोजें</button>
</form>
<div class="card" style="padding:0;overflow-x:auto">
<table style="width:100%;border-collapse:collapse;font-size:0.8rem;min-width:500px">
<tr style="background:var(--gray-100)"><th style="padding:8px;text-align:left">नाम</th><th>यूज़रनेम</th><th>भूमिका</th><th>स्थिति</th><th>कार्रवाई</th></tr>
<% users.forEach(u => { %>
<tr style="border-top:1px solid var(--gray-200)">
<td style="padding:8px"><%= u.name %></td>
<td style="padding:8px;color:var(--gray-700)"><%= u.username %></td>
<td style="padding:8px"><span class="badge badge-open"><%= u.role %></span></td>
<td style="padding:8px"><span class="badge badge-<%= u.is_active ? 'delivered' : 'cancelled' %>"><%= u.is_active ? 'सक्रिय' : 'निलंबित' %></span></td>
<td style="padding:8px">
<form method="POST" action="/admin/users/<%= u.id %>/suspend" style="display:inline">
<button class="btn btn-sm" style="padding:4px 8px;font-size:0.7rem;background:<%= u.is_active ? 'var(--red)' : 'var(--green)' %>;color:#fff"><%= u.is_active ? 'निलंबित' : 'सक्रिय' %></button>
</form>
</td>
</tr>
<% }) %>
</table>
</div> </div>
<% }) %>
</div> </div>
</section> </section>
<%- include('../partials/footer') %> <%- include('../partials/footer') %>

View file

@ -0,0 +1,26 @@
<% var title = 'Bank'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🏦 Bank Accounts</h2>
<% accounts.forEach(a => { %>
<div class="card" style="padding:12px;margin-bottom:8px;display:flex;justify-content:space-between;align-items:center">
<div><strong><%= a.bank_name %></strong><div style="font-size:0.75rem;color:var(--gray-700)"><%= a.account_holder || '' %> | <%= a.upi_id || a.account_number || '' %></div></div>
<form method="POST" action="/bank/delete/<%= a.id %>" style="margin:0"><button class="btn btn-sm" style="color:red">✕</button></form>
</div>
<% }) %>
<form method="POST" action="/bank/add" class="card" style="padding:var(--space-md);margin-top:var(--space-lg)">
<h4 style="margin-bottom:var(--space-sm)"> Add Account</h4>
<div style="display:grid;gap:var(--space-sm)">
<input type="text" name="bank_name" class="form-input" placeholder="🏦 Bank Name" required>
<input type="text" name="account_holder" class="form-input" placeholder="👤 Account Holder">
<input type="text" name="account_number" class="form-input" placeholder="Account Number">
<input type="text" name="ifsc" class="form-input" placeholder="IFSC Code">
<input type="text" name="upi_id" class="form-input" placeholder="📱 UPI ID (name@upi)">
<button type="submit" class="btn btn-primary btn-block">Save</button>
</div>
</form>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -1,25 +1,25 @@
<% var title = 'ब्रोकर डैशबोर्ड'; %> <% var title = t('dashboard.brokerTitle'); %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)"> <section class="section" style="padding-top:var(--space-lg)">
<div class="container"> <div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🤝 नमस्ते, <%= user.name %>!</h2> <h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🤝 <%= t('dashboard.hello') %>, <%= user.name %>!</h2>
<div class="stats-grid"> <div class="stats-grid">
<div class="stat-card"><div class="stat-value"><%= stats.totalLoads %></div><div class="stat-label">लोड पोस्ट</div></div> <div class="stat-card"><div class="stat-value"><%= stats.totalLoads %></div><div class="stat-label"><%= t('dashboard.loadsPosted') %></div></div>
<div class="stat-card"><div class="stat-value"><%= stats.bookedLoads %></div><div class="stat-label">सौदे</div></div> <div class="stat-card"><div class="stat-value"><%= stats.bookedLoads %></div><div class="stat-label"><%= t('dashboard.deals') %></div></div>
<div class="stat-card"><div class="stat-value"><%= stats.activeTrips %></div><div class="stat-label">सक्रिय</div></div> <div class="stat-card"><div class="stat-value"><%= stats.activeTrips %></div><div class="stat-label"><%= t('dashboard.activeTrips') %></div></div>
</div> </div>
<% if (recentLoads.length > 0) { %> <% if (recentLoads.length > 0) { %>
<h3 style="font-size:1rem;margin-top:var(--space-lg);margin-bottom:var(--space-sm)">📋 हाल के लोड</h3> <h3 style="font-size:1rem;margin-top:var(--space-lg);margin-bottom:var(--space-sm)">📋 <%= t('dashboard.recentLoads') %></h3>
<% recentLoads.forEach(load => { %> <% recentLoads.forEach(load => { %>
<a href="/loadboard/<%= load.id %>" class="card card-accent" style="display:block;text-decoration:none;color:inherit;margin-bottom:var(--space-sm)"> <a href="/loadboard/<%= load.id %>" class="card card-accent" style="display:block;text-decoration:none;color:inherit;margin-bottom:var(--space-sm)">
<div style="display:flex;justify-content:space-between;align-items:center"> <div style="display:flex;justify-content:space-between;align-items:center">
<div> <div>
<strong><%= load.origin_city %> → <%= load.destination_city %></strong> <strong><%= load.origin_city %> → <%= load.destination_city %></strong>
<div style="font-size:0.8rem;color:var(--gray-700)"><%= load.weight_tons %> टन | 🏷️ <%= load.bid_count %> बोली</div> <div style="font-size:0.8rem;color:var(--gray-700)"><%= load.weight_tons %> <%= t('common.tons') %> | 🏷️ <%= load.bid_count %> <%= t('common.bids') %></div>
</div> </div>
<span class="badge badge-<%= load.status === 'open' ? 'open' : 'booked' %>"><%= load.status %></span> <span class="badge badge-<%= load.status === 'open' ? 'open' : 'booked' %>"><%= load.status %></span>
</div> </div>
@ -27,9 +27,39 @@
<% }) %> <% }) %>
<% } %> <% } %>
<div style="margin-top:var(--space-lg);display:grid;gap:var(--space-sm)"> <div style="margin-top:var(--space-lg);display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)">
<a href="/loadboard/post" class="btn btn-cta btn-block">+ लोड पोस्ट करें</a> <a href="/loadboard/post" class="icon-action-btn">
<a href="/loadboard" class="btn btn-outline btn-block">📋 लोड बोर्ड</a> <span class="icon-action-emoji"></span>
<span class="icon-action-label"><%= t('actions.postLoad') %></span>
</a>
<a href="/loadboard" class="icon-action-btn">
<span class="icon-action-emoji">📋</span>
<span class="icon-action-label"><%= t('actions.viewLoads') %></span>
</a>
<a href="/invoice" class="icon-action-btn">
<span class="icon-action-emoji">🧾</span>
<span class="icon-action-label">Invoice</span>
</a>
<a href="/rates" class="icon-action-btn">
<span class="icon-action-emoji">📊</span>
<span class="icon-action-label">Rates</span>
</a>
<a href="/fleet" class="icon-action-btn">
<span class="icon-action-emoji">🚛</span>
<span class="icon-action-label">Fleet</span>
</a>
<a href="/classifieds" class="icon-action-btn">
<span class="icon-action-emoji">🛒</span>
<span class="icon-action-label">Buy/Sell</span>
</a>
<a href="/referral" class="icon-action-btn">
<span class="icon-action-emoji">🤝</span>
<span class="icon-action-label">Referral</span>
</a>
<a href="/notifications" class="icon-action-btn">
<span class="icon-action-emoji">🔔</span>
<span class="icon-action-label">Alerts</span>
</a>
</div> </div>
</div> </div>
</section> </section>

View file

@ -0,0 +1,30 @@
<% var title = 'Challenges'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🎯 Daily Challenges</h2>
<p style="color:var(--gray-700);font-size:0.85rem;margin-bottom:var(--space-md)">Complete tasks to earn XP! (<%= completedCount %>/3 done today)</p>
<div style="display:grid;gap:var(--space-sm)">
<% challenges.forEach(c => { %>
<div class="card card-accent" style="padding:16px;<%= c.completed ? 'opacity:0.6;border-left-color:var(--green)' : '' %>">
<div style="display:flex;justify-content:space-between;align-items:center">
<div style="display:flex;gap:12px;align-items:center">
<span style="font-size:1.5rem"><%= c.icon %></span>
<div>
<strong><%= c.title_hi %></strong>
<div style="font-size:0.75rem;color:var(--gray-700)"><%= c.title %> • +<%= c.xp %> XP</div>
</div>
</div>
<% if (c.completed) { %>
<span style="color:green;font-size:1.2rem">✅</span>
<% } else { %>
<form method="POST" action="/challenges/complete" style="margin:0"><input type="hidden" name="challenge_id" value="<%= c.id %>"><button class="btn btn-sm btn-primary">Done</button></form>
<% } %>
</div>
</div>
<% }) %>
</div>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -1,29 +1,24 @@
<% var title = otherUser.name + ' — चैट'; %> <% var title = t('nav.messages'); %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg);padding-bottom:80px">
<section class="section" style="padding-top:var(--space-lg)"> <div class="container" style="max-width:600px">
<div class="container" style="max-width:500px"> <a href="/messages" style="font-size:0.8rem;color:var(--gray-700)">← <%= t('nav.messages') %></a>
<div style="display:flex;align-items:center;gap:var(--space-sm);margin-bottom:var(--space-md)"> <h3 style="margin:8px 0 var(--space-md)">💬 <%= otherUser.name || otherUser.username %></h3>
<a href="/messages" style="font-size:1.2rem">←</a> <div style="display:flex;flex-direction:column;gap:8px;margin-bottom:var(--space-lg)">
<strong><%= otherUser.name %></strong> <% messages.forEach(m => { const isMine = m.sender_id === user.id; %>
<span style="font-size:0.75rem;color:var(--gray-700)">@<%= otherUser.username %></span> <div style="align-self:<%= isMine ? 'flex-end' : 'flex-start' %>;background:<%= isMine ? 'var(--navy)' : 'var(--gray-100)' %>;color:<%= isMine ? '#fff' : 'inherit' %>;padding:10px 14px;border-radius:12px;max-width:80%;font-size:0.85rem">
</div> <%= m.content %>
<div style="font-size:0.65rem;opacity:0.7;margin-top:4px"><%= new Date(m.created_at).toLocaleTimeString('en-IN',{hour:'2-digit',minute:'2-digit'}) %></div>
<div style="display:flex;flex-direction:column;gap:var(--space-sm);margin-bottom:var(--space-md);max-height:400px;overflow-y:auto"> </div>
<% messages.forEach(m => { %>
<div style="align-self:<%= m.sender_id === user.id ? 'flex-end' : 'flex-start' %>;max-width:80%;padding:8px 12px;border-radius:12px;font-size:0.85rem;background:<%= m.sender_id === user.id ? 'var(--navy)' : 'var(--gray-200)' %>;color:<%= m.sender_id === user.id ? '#fff' : 'var(--gray-900)' %>">
<%= m.content %>
<div style="font-size:0.6rem;opacity:0.7;margin-top:2px"><%= new Date(m.created_at).toLocaleTimeString('hi-IN', {hour:'2-digit',minute:'2-digit'}) %></div>
</div>
<% }) %> <% }) %>
</div> </div>
<form method="POST" action="/messages/<%= otherUser.id %>" style="position:fixed;bottom:70px;left:0;right:0;padding:8px 16px;background:#fff;border-top:1px solid var(--gray-200)">
<form method="POST" action="/messages/<%= otherId %>" style="display:flex;gap:var(--space-sm)"> <div style="display:grid;grid-template-columns:1fr auto;gap:8px">
<input type="text" name="content" class="form-input" placeholder="संदेश लिखें..." required autofocus style="flex:1;padding:10px 14px"> <input type="text" name="content" class="form-input" placeholder="<%= t('messages.typeHere') %>" required autofocus>
<button type="submit" class="btn btn-primary">भेजें</button> <button type="submit" class="btn btn-primary">📤</button>
</div>
</form> </form>
</div> </div>
</section> </section>
<%- include('../partials/footer') %> <%- include('../partials/footer') %>

View file

@ -0,0 +1,22 @@
<% var title = 'Post Ad'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container" style="max-width:500px">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">📝 Post Ad</h2>
<form method="POST" action="/classifieds/post" class="card" style="padding:var(--space-md)">
<div style="display:grid;gap:var(--space-sm)">
<input type="text" name="title" class="form-input" placeholder="Title (e.g. Tata 407 for sale)" required>
<textarea name="description" class="form-input" placeholder="Description" rows="3"></textarea>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-sm)">
<input type="number" name="price" class="form-input" placeholder="₹ Price" required>
<select name="category" class="form-input form-select"><option value="truck">🚛 Truck</option><option value="parts">🔧 Parts</option><option value="tyres">⭕ Tyres</option><option value="other">📦 Other</option></select>
</div>
<input type="text" name="location" class="form-input" placeholder="📍 Location">
<input type="tel" name="contact_phone" class="form-input" placeholder="📱 Contact Phone">
<button type="submit" class="btn btn-primary btn-block">Post Ad</button>
</div>
</form>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,28 @@
<% var title = 'Buy/Sell'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-md)">
<h2 style="font-size:1.3rem">🛒 Buy / Sell</h2>
<a href="/classifieds/post" class="btn btn-cta btn-sm">+ Post</a>
</div>
<div style="display:flex;gap:8px;margin-bottom:var(--space-md);flex-wrap:wrap">
<a href="/classifieds" class="btn btn-sm <%= category==='all'?'btn-primary':'btn-outline' %>">All</a>
<a href="/classifieds?category=truck" class="btn btn-sm <%= category==='truck'?'btn-primary':'btn-outline' %>">🚛 Trucks</a>
<a href="/classifieds?category=parts" class="btn btn-sm <%= category==='parts'?'btn-primary':'btn-outline' %>">🔧 Parts</a>
<a href="/classifieds?category=tyres" class="btn btn-sm <%= category==='tyres'?'btn-primary':'btn-outline' %>">⭕ Tyres</a>
</div>
<% if (listings.length === 0) { %>
<div class="card text-center" style="padding:var(--space-2xl)"><p>No listings yet.</p></div>
<% } else { listings.forEach(l => { %>
<div class="card card-accent" style="margin-bottom:var(--space-sm)">
<div style="display:flex;justify-content:space-between;align-items:start">
<div><strong><%= l.title %></strong><div style="font-size:0.8rem;color:var(--gray-700)">📍 <%= l.location || '' %> | <%= l.category %></div></div>
<div style="font-weight:700;color:var(--navy)">₹<%= (l.price||0).toLocaleString('en-IN') %></div>
</div>
</div>
<% }) } %>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,25 @@
<% var title = 'Documents'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">📄 Document Vault</h2>
<% documents.forEach(d => { %>
<div class="card" style="padding:12px;margin-bottom:8px;display:flex;justify-content:space-between;align-items:center">
<div><strong><%= d.doc_type.toUpperCase() %></strong> — <%= d.vehicle_number %><div style="font-size:0.75rem;color:var(--gray-700)"><%= d.doc_number || '' %> <% if(d.expiry_date){%>| Exp: <%= d.expiry_date %><%}%></div></div>
<form method="POST" action="/documents/delete/<%= d.id %>" style="margin:0"><button class="btn btn-sm" style="color:red">✕</button></form>
</div>
<% }) %>
<form method="POST" action="/documents/add" class="card" style="padding:var(--space-md);margin-top:var(--space-lg)">
<h4 style="margin-bottom:var(--space-sm)"> Add Document</h4>
<div style="display:grid;gap:var(--space-sm)">
<select name="doc_type" class="form-input form-select" required><option value="rc">RC</option><option value="insurance">Insurance</option><option value="permit">Permit</option><option value="license">License</option><option value="puc">PUC</option><option value="fitness">Fitness</option></select>
<input type="text" name="vehicle_number" class="form-input" placeholder="🚛 Vehicle Number" required>
<input type="text" name="doc_number" class="form-input" placeholder="Document Number">
<input type="date" name="expiry_date" class="form-input">
<button type="submit" class="btn btn-primary btn-block">Save</button>
</div>
</form>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -1,19 +1,19 @@
<% var title = 'ड्राइवर डैशबोर्ड'; %> <% var title = 'Driver Dashboard'; %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)"> <section class="section" style="padding-top:var(--space-lg)">
<div class="container"> <div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🚛 नमस्ते, <%= user.name %>!</h2> <h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🚛 <%= t('dashboard.hello') %>, <%= user.name %>!</h2>
<div class="stats-grid"> <div class="stats-grid">
<div class="stat-card"><div class="stat-value"><%= stats.totalTrips %></div><div class="stat-label">कुल ट्रिप</div></div> <div class="stat-card"><div class="stat-value"><%= stats.totalTrips %></div><div class="stat-label"><%= t('dashboard.totalTrips') %></div></div>
<div class="stat-card"><div class="stat-value"><%= stats.activeBids %></div><div class="stat-label">सक्रिय बोलियाँ</div></div> <div class="stat-card"><div class="stat-value"><%= stats.activeBids %></div><div class="stat-label"><%= t('dashboard.activeBids') %></div></div>
<div class="stat-card"><div class="stat-value">₹<%= stats.earnings.toLocaleString('en-IN') %></div><div class="stat-label">कमाई</div></div> <div class="stat-card"><div class="stat-value">₹<%= stats.earnings.toLocaleString('en-IN') %></div><div class="stat-label"><%= t('dashboard.earnings') %></div></div>
</div> </div>
<% if (activeTrips.length > 0) { %> <% if (activeTrips.length > 0) { %>
<h3 style="font-size:1rem;margin-top:var(--space-lg);margin-bottom:var(--space-sm)">🔄 सक्रिय ट्रिप</h3> <h3 style="font-size:1rem;margin-top:var(--space-lg);margin-bottom:var(--space-sm)">🔄 <%= t('dashboard.activeTrips') %></h3>
<% activeTrips.forEach(trip => { %> <% activeTrips.forEach(trip => { %>
<div class="card card-accent" style="margin-bottom:var(--space-sm)"> <div class="card card-accent" style="margin-bottom:var(--space-sm)">
<div style="display:flex;justify-content:space-between;align-items:center"> <div style="display:flex;justify-content:space-between;align-items:center">
@ -27,9 +27,39 @@
<% }) %> <% }) %>
<% } %> <% } %>
<div style="margin-top:var(--space-lg);display:grid;gap:var(--space-sm)"> <div style="margin-top:var(--space-lg);display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)">
<a href="/loadboard" class="btn btn-primary btn-block">📋 लोड बोर्ड देखें</a> <a href="/loadboard" class="icon-action-btn">
<a href="/trips" class="btn btn-outline btn-block">🚚 मेरी ट्रिप</a> <span class="icon-action-emoji">📋</span>
<span class="icon-action-label"><%= t('actions.viewLoads') %></span>
</a>
<a href="/driver/ledger" class="icon-action-btn">
<span class="icon-action-emoji">📒</span>
<span class="icon-action-label">Ledger</span>
</a>
<a href="/trip-planner" class="icon-action-btn">
<span class="icon-action-emoji">🧮</span>
<span class="icon-action-label">Trip Cost</span>
</a>
<a href="/returnload" class="icon-action-btn">
<span class="icon-action-emoji">🔄</span>
<span class="icon-action-label">Return Load</span>
</a>
<a href="/safety" class="icon-action-btn">
<span class="icon-action-emoji">🛡️</span>
<span class="icon-action-label">Safety</span>
</a>
<a href="/maintenance" class="icon-action-btn">
<span class="icon-action-emoji">🔧</span>
<span class="icon-action-label">Reminders</span>
</a>
<a href="/fastag" class="icon-action-btn">
<span class="icon-action-emoji">🏷️</span>
<span class="icon-action-label">FASTag</span>
</a>
<a href="/notifications" class="icon-action-btn">
<span class="icon-action-emoji">🔔</span>
<span class="icon-action-label">Alerts</span>
</a>
</div> </div>
</div> </div>
</section> </section>

View file

@ -0,0 +1,25 @@
<% var title = 'Add Trip'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container" style="max-width:500px">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)"> Add Trip to Ledger</h2>
<form method="POST" action="/driver/ledger/add" class="card" style="padding:var(--space-md)">
<div style="display:grid;gap:var(--space-sm)">
<div><label class="form-label">📍 From</label><input type="text" name="origin" class="form-input" placeholder="City" required></div>
<div><label class="form-label">📍 To</label><input type="text" name="destination" class="form-input" placeholder="City" required></div>
<div><label class="form-label">📅 Date</label><input type="date" name="trip_date" class="form-input" value="<%= new Date().toISOString().split('T')[0] %>"></div>
<div><label class="form-label">💰 Freight Received ₹</label><input type="number" name="freight_received" class="form-input" placeholder="25000" required></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-sm)">
<div><label class="form-label">⛽ Fuel ₹</label><input type="number" name="fuel_cost" class="form-input" placeholder="8000"></div>
<div><label class="form-label">🚧 Toll ₹</label><input type="number" name="toll_cost" class="form-input" placeholder="3000"></div>
</div>
<div><label class="form-label">📦 Other ₹</label><input type="number" name="other_expense" class="form-input" placeholder="1000"></div>
<div><label class="form-label">📝 Notes</label><input type="text" name="notes" class="form-input" placeholder="Optional"></div>
<button type="submit" class="btn btn-primary btn-block btn-lg">💾 Save Trip</button>
</div>
</form>
<a href="/driver/ledger" class="btn btn-outline btn-block" style="margin-top:var(--space-sm)">← Back</a>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,34 @@
<% var title = 'My Ledger'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-md)">
<h2 style="font-size:1.3rem">📒 My Ledger</h2>
<a href="/driver/ledger/add" class="btn btn-cta btn-sm">+ Add Trip</a>
</div>
<div class="stats-grid" style="margin-bottom:var(--space-md)">
<div class="stat-card"><div class="stat-value"><%= stats.total_trips %></div><div class="stat-label">🚛 Trips</div></div>
<div class="stat-card"><div class="stat-value">₹<%= stats.total_earned.toLocaleString('en-IN') %></div><div class="stat-label">💰 Earned</div></div>
<div class="stat-card"><div class="stat-value">₹<%= stats.total_expenses.toLocaleString('en-IN') %></div><div class="stat-label">💸 Expenses</div></div>
<div class="stat-card"><div class="stat-value" style="color:<%= stats.net_profit >= 0 ? 'green' : 'red' %>">₹<%= stats.net_profit.toLocaleString('en-IN') %></div><div class="stat-label">📊 Profit</div></div>
</div>
<% if (entries.length === 0) { %>
<div class="card text-center" style="padding:var(--space-2xl)"><p>📭 No entries yet. Add your first trip!</p></div>
<% } else { entries.forEach(e => { %>
<div class="card card-accent" style="margin-bottom:var(--space-sm)">
<div style="display:flex;justify-content:space-between;align-items:start">
<div>
<strong>📍 <%= e.origin %> → <%= e.destination %></strong>
<div style="font-size:0.8rem;color:var(--gray-700);margin-top:4px">📅 <%= e.trip_date %> | ⛽ ₹<%= (e.fuel_cost||0).toLocaleString('en-IN') %> | 🚧 ₹<%= (e.toll_cost||0).toLocaleString('en-IN') %></div>
</div>
<div style="text-align:right">
<div style="font-weight:700;color:green">+₹<%= (e.freight_received||0).toLocaleString('en-IN') %></div>
<form method="POST" action="/driver/ledger/delete/<%= e.id %>" style="margin:0"><button class="btn btn-sm" style="color:red;font-size:0.7rem">✕</button></form>
</div>
</div>
</div>
<% }) } %>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,40 @@
<% var title = 'FASTag'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🏷️ FASTag & Tolls</h2>
<% if (!fastag) { %>
<form method="POST" action="/fastag/register" class="card" style="padding:var(--space-md)">
<h4 style="margin-bottom:var(--space-sm)">Register FASTag</h4>
<div style="display:grid;gap:var(--space-sm)">
<input type="text" name="fastag_number" class="form-input" placeholder="FASTag Number" required>
<input type="text" name="vehicle_number" class="form-input" placeholder="🚛 Vehicle Number" required>
<input type="text" name="issuer_bank" class="form-input" placeholder="🏦 Bank (optional)">
<button type="submit" class="btn btn-primary btn-block">Register</button>
</div>
</form>
<% } else { %>
<div class="stats-grid" style="margin-bottom:var(--space-md)">
<div class="stat-card"><div class="stat-value">₹<%= stats.balance.toLocaleString('en-IN') %></div><div class="stat-label">💳 Balance</div></div>
<div class="stat-card"><div class="stat-value">₹<%= stats.month_spend.toLocaleString('en-IN') %></div><div class="stat-label">📅 This Month</div></div>
</div>
<form method="POST" action="/fastag/log-toll" class="card" style="padding:var(--space-md);margin-bottom:var(--space-md)">
<h4 style="margin-bottom:var(--space-sm)">🚧 Log Toll</h4>
<div style="display:grid;grid-template-columns:2fr 1fr auto;gap:var(--space-sm);align-items:end">
<input type="text" name="plaza_name" class="form-input" placeholder="Toll Name">
<input type="number" name="amount" class="form-input" placeholder="₹" required>
<button type="submit" class="btn btn-primary btn-sm">+</button>
</div>
</form>
<h3 style="font-size:1rem;margin-bottom:var(--space-sm)">📋 Recent</h3>
<% history.slice(0,15).forEach(h => { %>
<div class="card" style="padding:10px;margin-bottom:6px;display:flex;justify-content:space-between">
<div><strong><%= h.type==='toll'?'🚧':'💳' %> <%= h.plaza_name || h.type %></strong><div style="font-size:0.7rem;color:var(--gray-700)"><%= new Date(h.created_at).toLocaleDateString('en-IN') %></div></div>
<span style="font-weight:700;color:<%= h.type==='toll'?'red':'green' %>"><%= h.type==='toll'?'-':'+' %>₹<%= (h.amount||0).toLocaleString('en-IN') %></span>
</div>
<% }) %>
<% } %>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,23 @@
<% var title = 'Activity Feed'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">📰 Activity Feed</h2>
<% if (events.length === 0) { %>
<div class="card text-center" style="padding:var(--space-2xl)"><p>No activity yet. Be the first!</p></div>
<% } else { events.forEach(e => { const d = e.data || {}; %>
<div class="card" style="padding:12px;margin-bottom:8px">
<div style="display:flex;gap:10px;align-items:start">
<span style="font-size:1.3rem"><%= e.event_type==='bid_placed'?'🏷️':e.event_type==='load_posted'?'📦':e.event_type==='trip_completed'?'✅':'📣' %></span>
<div>
<strong style="font-size:0.85rem"><%= d.title || e.event_type %></strong>
<% if (d.subtitle) { %><div style="font-size:0.75rem;color:var(--gray-700)"><%= d.subtitle %></div><% } %>
<div style="font-size:0.7rem;color:var(--gray-500);margin-top:2px"><%= new Date(e.created_at).toLocaleString('en-IN') %></div>
</div>
</div>
</div>
<% }) } %>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,33 @@
<% var title = 'Fleet'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🚛 My Fleet</h2>
<% vehicles.forEach(v => { %>
<div class="card card-accent" style="margin-bottom:var(--space-sm)">
<div style="display:flex;justify-content:space-between;align-items:center">
<div>
<strong><%= v.vehicle_number %></strong> — <%= v.vehicle_type %>
<div style="font-size:0.8rem;color:var(--gray-700)"><%= v.driver_name || 'No driver' %> | <%= v.capacity_tons %> tons</div>
</div>
<span class="badge badge-<%= v.status==='available'?'open':v.status==='on_trip'?'transit':'booked' %>"><%= v.status %></span>
</div>
</div>
<% }) %>
<form method="POST" action="/fleet/add" class="card" style="padding:var(--space-md);margin-top:var(--space-lg)">
<h4 style="margin-bottom:var(--space-sm)"> Add Vehicle</h4>
<div style="display:grid;gap:var(--space-sm)">
<input type="text" name="vehicle_number" class="form-input" placeholder="🚛 Vehicle Number" required>
<select name="vehicle_type" class="form-input form-select"><option value="open">Open</option><option value="container">Container</option><option value="trailer">Trailer</option><option value="tanker">Tanker</option></select>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-sm)">
<input type="text" name="driver_name" class="form-input" placeholder="👤 Driver Name">
<input type="tel" name="driver_phone" class="form-input" placeholder="📱 Phone">
</div>
<input type="number" name="capacity_tons" class="form-input" placeholder="⚖️ Capacity (tons)">
<button type="submit" class="btn btn-primary btn-block">Add Vehicle</button>
</div>
</form>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,15 @@
<% var title = 'Games'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🎮 Mini Games</h2>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)">
<a href="/games/rate-guesser" class="icon-action-btn"><span class="icon-action-emoji">💰</span><span class="icon-action-label">Rate Guesser</span></a>
<a href="/games/route-quiz" class="icon-action-btn"><span class="icon-action-emoji">🗺️</span><span class="icon-action-label">Route Quiz</span></a>
<a href="/challenges" class="icon-action-btn"><span class="icon-action-emoji">🎯</span><span class="icon-action-label">Challenges</span></a>
<a href="/leaderboard" class="icon-action-btn"><span class="icon-action-emoji">🏆</span><span class="icon-action-label">Leaderboard</span></a>
</div>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,32 @@
<% var title = 'Rate Guesser'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container" style="max-width:500px;text-align:center">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">💰 Rate Guesser</h2>
<div class="card" style="padding:var(--space-lg)">
<p style="font-size:0.85rem;color:var(--gray-700)">Guess the freight rate:</p>
<h3 style="margin:12px 0">📍 <%= load.origin_city %> → <%= load.destination_city %></h3>
<p style="font-size:0.9rem">⚖️ <%= load.weight_tons %> tons</p>
<% if (!revealed) { %>
<form method="POST" action="/games/rate-guesser/guess" style="margin-top:var(--space-md)">
<input type="hidden" name="actual" value="<%= load.budget %>">
<input type="hidden" name="origin" value="<%= load.origin_city %>">
<input type="hidden" name="destination" value="<%= load.destination_city %>">
<input type="hidden" name="weight" value="<%= load.weight_tons %>">
<input type="number" name="guess" class="form-input" placeholder="₹ Your guess" required style="text-align:center;font-size:1.2rem">
<button type="submit" class="btn btn-primary btn-block" style="margin-top:var(--space-sm)">Submit Guess</button>
</form>
<% } else { %>
<div style="margin-top:var(--space-md)">
<p>Your guess: <strong>₹<%= guess.toLocaleString('en-IN') %></strong></p>
<p>Actual rate: <strong style="color:var(--navy)">₹<%= load.budget.toLocaleString('en-IN') %></strong></p>
<div style="margin:12px 0;font-size:1.5rem;font-weight:700;color:<%= accuracy >= 70 ? 'green' : accuracy >= 50 ? 'orange' : 'red' %>"><%= accuracy %>% accurate</div>
<p style="color:var(--gray-700)">+<%= xpEarned %> XP earned! 🎉</p>
</div>
<a href="/games/rate-guesser" class="btn btn-primary btn-block" style="margin-top:var(--space-md)">Play Again</a>
<% } %>
</div>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,30 @@
<% var title = 'Route Quiz'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container" style="max-width:500px;text-align:center">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🗺️ Route Quiz</h2>
<div class="card" style="padding:var(--space-lg)">
<p style="font-size:0.85rem;color:var(--gray-700)">Guess the distance (km):</p>
<h3 style="margin:12px 0">📍 <%= origin %> → <%= destination %></h3>
<% if (!revealed) { %>
<form method="POST" action="/games/route-quiz/guess" style="margin-top:var(--space-md)">
<input type="hidden" name="actual_km" value="<%= actual_km %>">
<input type="hidden" name="origin" value="<%= origin %>">
<input type="hidden" name="destination" value="<%= destination %>">
<input type="number" name="guess" class="form-input" placeholder="Distance in km" required style="text-align:center;font-size:1.2rem">
<button type="submit" class="btn btn-primary btn-block" style="margin-top:var(--space-sm)">Submit</button>
</form>
<% } else { %>
<div style="margin-top:var(--space-md)">
<p>Your guess: <strong><%= guess %> km</strong></p>
<p>Actual: <strong style="color:var(--navy)"><%= actual_km %> km</strong></p>
<div style="margin:12px 0;font-size:1.5rem;font-weight:700;color:<%= accuracy >= 70 ? 'green' : accuracy >= 50 ? 'orange' : 'red' %>"><%= accuracy %>% accurate</div>
<p style="color:var(--gray-700)">+<%= xpEarned %> XP earned! 🎉</p>
</div>
<a href="/games/route-quiz" class="btn btn-primary btn-block" style="margin-top:var(--space-md)">Play Again</a>
<% } %>
</div>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,28 @@
<% var title = 'My Level'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<div class="card" style="padding:var(--space-lg);text-align:center;margin-bottom:var(--space-md)">
<div style="font-size:3rem"><%= level.icon %></div>
<h2 style="margin:8px 0 4px">Level <%= level.level %> — <%= level.title %></h2>
<p style="color:var(--gray-700);font-size:0.85rem"><%= xp %> XP</p>
<div style="background:var(--gray-200);border-radius:20px;height:12px;margin-top:12px;overflow:hidden">
<div style="background:linear-gradient(90deg,var(--saffron),var(--ashoka-blue));height:100%;width:<%= level.progress %>%;border-radius:20px"></div>
</div>
<p style="font-size:0.75rem;color:var(--gray-700);margin-top:4px"><%= level.progress %>% to Level <%= level.level + 1 %></p>
<p style="font-size:0.85rem;margin-top:8px">🔥 Streak: <%= streak %> days</p>
</div>
<h3 style="font-size:1rem;margin-bottom:var(--space-sm)">🏆 Achievements</h3>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:var(--space-sm)">
<% achievements.forEach(a => { %>
<div class="card text-center" style="padding:12px;opacity:<%= a.earned ? 1 : 0.4 %>">
<div style="font-size:1.5rem"><%= a.icon %></div>
<div style="font-size:0.7rem;font-weight:700;margin-top:4px"><%= a.title %></div>
<div style="font-size:0.65rem;color:var(--gray-700)">+<%= a.xp %> XP</div>
</div>
<% }) %>
</div>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,25 @@
<% var title = 'New Invoice'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container" style="max-width:500px">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🧾 Create Invoice</h2>
<form method="POST" action="/invoice/create" class="card" style="padding:var(--space-md)">
<div style="display:grid;gap:var(--space-sm)">
<div><label class="form-label">👤 Client Name</label><input type="text" name="client_name" class="form-input" required></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-sm)">
<div><label class="form-label">📍 From</label><input type="text" name="origin" class="form-input" required></div>
<div><label class="form-label">📍 To</label><input type="text" name="destination" class="form-input" required></div>
</div>
<div style="display:grid;grid-template-columns:2fr 1fr;gap:var(--space-sm)">
<div><label class="form-label">💰 Amount ₹</label><input type="number" name="amount" class="form-input" required></div>
<div><label class="form-label">GST %</label><input type="number" name="gst_rate" class="form-input" value="5"></div>
</div>
<div><label class="form-label">📱 UPI ID (for payment link)</label><input type="text" name="upi_id" class="form-input" placeholder="name@upi"></div>
<div><label class="form-label">📝 Notes</label><input type="text" name="notes" class="form-input"></div>
<button type="submit" class="btn btn-primary btn-block btn-lg">Generate Invoice</button>
</div>
</form>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,25 @@
<% var title = 'Invoice ' + invoice.invoice_number; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container" style="max-width:500px">
<div class="card" style="padding:var(--space-lg)">
<div style="text-align:center;margin-bottom:var(--space-md)"><h3>🧾 INVOICE</h3><p style="font-size:0.8rem;color:var(--gray-700)"><%= invoice.invoice_number %></p></div>
<div style="display:grid;gap:8px;font-size:0.9rem">
<div><strong>Client:</strong> <%= invoice.client_name %></div>
<div><strong>Route:</strong> <%= invoice.origin %> → <%= invoice.destination %></div>
<hr style="border:none;border-top:1px solid var(--gray-200)">
<div style="display:flex;justify-content:space-between"><span>Amount:</span><span>₹<%= (invoice.amount||0).toLocaleString('en-IN') %></span></div>
<div style="display:flex;justify-content:space-between"><span>GST (<%= invoice.gst_rate %>%):</span><span>₹<%= (invoice.gst_amount||0).toLocaleString('en-IN') %></span></div>
<hr style="border:none;border-top:2px solid var(--navy)">
<div style="display:flex;justify-content:space-between;font-size:1.1rem;font-weight:700"><span>Total:</span><span>₹<%= (invoice.total_amount||0).toLocaleString('en-IN') %></span></div>
</div>
<% if (invoice.upi_link) { %>
<a href="<%= invoice.upi_link %>" class="btn btn-primary btn-block" style="margin-top:var(--space-lg)">💳 Pay via UPI</a>
<% } %>
<% if (invoice.notes) { %><p style="font-size:0.8rem;color:var(--gray-700);margin-top:var(--space-md)">📝 <%= invoice.notes %></p><% } %>
</div>
<a href="/invoice" class="btn btn-outline btn-block" style="margin-top:var(--space-sm)">← Back</a>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,28 @@
<% var title = 'Invoices'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-md)">
<h2 style="font-size:1.3rem">🧾 Invoices</h2>
<a href="/invoice/create" class="btn btn-cta btn-sm">+ New</a>
</div>
<% if (invoices.length === 0) { %>
<div class="card text-center" style="padding:var(--space-2xl)"><p>No invoices yet.</p></div>
<% } else { invoices.forEach(inv => { %>
<a href="/invoice/<%= inv.id %>" class="card card-accent" style="display:block;text-decoration:none;color:inherit;margin-bottom:var(--space-sm)">
<div style="display:flex;justify-content:space-between;align-items:center">
<div>
<strong><%= inv.invoice_number %></strong> — <%= inv.client_name %>
<div style="font-size:0.8rem;color:var(--gray-700)"><%= inv.origin %> → <%= inv.destination %></div>
</div>
<div style="text-align:right">
<div style="font-weight:700">₹<%= (inv.total_amount||0).toLocaleString('en-IN') %></div>
<span class="badge badge-<%= inv.status==='paid'?'open':'transit' %>"><%= inv.status %></span>
</div>
</div>
</a>
<% }) } %>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -1,41 +1,41 @@
<% var title = 'राष्ट्रीय माल परिवहन मंच'; %> <% var title = t('common.subtitle'); %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="hero"> <section class="hero">
<div class="hero-badge">🇮🇳 भारत सरकार पंजीकृत मंच | Registered Platform</div> <div class="hero-badge">🇮🇳 <%= t('landing.badge') %></div>
<h1>ट्रक ड्राइवर। शिपर। ब्रोकर।<br><span class="highlight">सबके लिए मुफ्त।</span></h1> <h1><%= t('landing.heroTitle') %><br><span class="highlight"><%= t('landing.heroHighlight') %></span></h1>
<p class="hero-sub">भारत का राष्ट्रीय माल परिवहन मंच — लोड पोस्ट करें, बोली लगाएं, कमाई करें। बिना किसी शुल्क के।</p> <p class="hero-sub"><%= t('landing.heroSub') %></p>
<div class="hero-ctas"> <div class="hero-ctas">
<a href="/register" class="btn btn-cta btn-lg">मुफ्त पंजीकरण करें</a> <a href="/register" class="btn btn-cta btn-lg"><%= t('auth.registerBtn') %></a>
<a href="/loadboard" class="btn btn-outline btn-lg" style="border-color:rgba(255,255,255,0.5);color:#fff">लोड बोर्ड देखें</a> <a href="/loadboard" class="btn btn-outline btn-lg" style="border-color:rgba(255,255,255,0.5);color:#fff"><%= t('actions.viewLoads') %></a>
</div> </div>
<div class="hero-stats"> <div class="hero-stats">
<div class="hero-stat"><div class="hero-stat-num">मुफ्त</div><div class="hero-stat-label">हमेशा के लिए</div></div> <div class="hero-stat"><div class="hero-stat-num"><%= t('landing.free') %></div><div class="hero-stat-label"><%= t('landing.forever') %></div></div>
<div class="hero-stat"><div class="hero-stat-num">30 सेकंड</div><div class="hero-stat-label">पंजीकरण</div></div> <div class="hero-stat"><div class="hero-stat-num">30 <%= t('landing.seconds') %></div><div class="hero-stat-label"><%= t('actions.register') %></div></div>
<div class="hero-stat"><div class="hero-stat-num">5 मिनट</div><div class="hero-stat-label">पहली बोली</div></div> <div class="hero-stat"><div class="hero-stat-num">5 <%= t('landing.minutes') %></div><div class="hero-stat-label"><%= t('landing.firstBid') %></div></div>
</div> </div>
</section> </section>
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2 class="section-title">एक मंच। तीन उपयोगकर्ता।</h2> <h2 class="section-title"><%= t('landing.onePlatform') %></h2>
<p class="section-subtitle">चाहे आप माल भेजें, ट्रक चलाएं, या सौदे कराएं — भारत ट्रक्स आपके लिए है।</p> <p class="section-subtitle"><%= t('landing.onePlatformSub') %></p>
<div class="roles-grid"> <div class="roles-grid">
<div class="role-card role-card-driver"> <div class="role-card role-card-driver">
<div class="role-icon">🚛</div> <div class="role-icon">🚛</div>
<h3>ट्रक ड्राइवर</h3> <h3><%= t('auth.driver') %></h3>
<ul><li>लोड खोजें और बोली लगाएं</li><li>खाली वापसी से बचें</li><li>कमाई का हिसाब रखें</li><li>सीधे शिपर से जुड़ें</li></ul> <ul><li><%= t('landing.driverF1') %></li><li><%= t('landing.driverF2') %></li><li><%= t('landing.driverF3') %></li><li><%= t('landing.driverF4') %></li></ul>
</div> </div>
<div class="role-card role-card-shipper"> <div class="role-card role-card-shipper">
<div class="role-icon">📦</div> <div class="role-icon">📦</div>
<h3>शिपर / माल भेजने वाले</h3> <h3><%= t('auth.shipper') %></h3>
<ul><li>लोड पोस्ट करें, बोली पाएं</li><li>सत्यापित ड्राइवर चुनें</li><li>माल की स्थिति जानें</li><li>भुगतान का रिकॉर्ड रखें</li></ul> <ul><li><%= t('landing.shipperF1') %></li><li><%= t('landing.shipperF2') %></li><li><%= t('landing.shipperF3') %></li><li><%= t('landing.shipperF4') %></li></ul>
</div> </div>
<div class="role-card role-card-broker"> <div class="role-card role-card-broker">
<div class="role-icon">🤝</div> <div class="role-icon">🤝</div>
<h3>ब्रोकर / एजेंट</h3> <h3><%= t('auth.broker') %></h3>
<ul><li>अपने नेटवर्क को डिजिटल करें</li><li>कमीशन ट्रैक करें</li><li>शिपर के लिए लोड पोस्ट करें</li><li>ड्राइवर नेटवर्क बढ़ाएं</li></ul> <ul><li><%= t('landing.brokerF1') %></li><li><%= t('landing.brokerF2') %></li><li><%= t('landing.brokerF3') %></li><li><%= t('landing.brokerF4') %></li></ul>
</div> </div>
</div> </div>
</div> </div>
@ -43,34 +43,34 @@
<section class="section" style="background:var(--gray-50)"> <section class="section" style="background:var(--gray-50)">
<div class="container"> <div class="container">
<h2 class="section-title">कैसे काम करता है?</h2> <h2 class="section-title"><%= t('landing.howTitle') %></h2>
<p class="section-subtitle">सिर्फ 4 आसान कदम</p> <p class="section-subtitle"><%= t('landing.howSub') %></p>
<div class="steps-grid"> <div class="steps-grid">
<div class="step-card"><h4>पंजीकरण करें</h4><p>फोन नंबर से मुफ्त अकाउंट बनाएं। अपनी भूमिका चुनें।</p></div> <div class="step-card"><h4><%= t('landing.step1') %></h4><p><%= t('landing.step1Desc') %></p></div>
<div class="step-card"><h4>लोड पोस्ट / खोजें</h4><p>शिपर लोड पोस्ट करें। ड्राइवर उपलब्ध लोड देखें।</p></div> <div class="step-card"><h4><%= t('landing.step2') %></h4><p><%= t('landing.step2Desc') %></p></div>
<div class="step-card"><h4>बोली लगाएं / स्वीकार करें</h4><p>ड्राइवर अपनी कीमत बताएं। शिपर सबसे अच्छी बोली चुनें।</p></div> <div class="step-card"><h4><%= t('landing.step3') %></h4><p><%= t('landing.step3Desc') %></p></div>
<div class="step-card"><h4>माल पहुँचाएं, भुगतान पाएं</h4><p>ट्रिप पूरी करें। UPI से सीधे भुगतान पाएं।</p></div> <div class="step-card"><h4><%= t('landing.step4') %></h4><p><%= t('landing.step4Desc') %></p></div>
</div> </div>
</div> </div>
</section> </section>
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<h2 class="section-title">क्यों भारत ट्रक्स?</h2> <h2 class="section-title"><%= t('landing.whyTitle') %></h2>
<div class="stats-grid"> <div class="stats-grid">
<div class="stat-card"><div class="stat-value">₹0</div><div class="stat-label">कोई शुल्क नहीं</div></div> <div class="stat-card"><div class="stat-value">₹0</div><div class="stat-label"><%= t('landing.noFee') %></div></div>
<div class="stat-card"><div class="stat-value">🔒</div><div class="stat-label">सुरक्षित मंच</div></div> <div class="stat-card"><div class="stat-value">🔒</div><div class="stat-label"><%= t('landing.secure') %></div></div>
<div class="stat-card"><div class="stat-value">📱</div><div class="stat-label">मोबाइल पर चलता है</div></div> <div class="stat-card"><div class="stat-value">📱</div><div class="stat-label"><%= t('landing.mobile') %></div></div>
<div class="stat-card"><div class="stat-value">🇮🇳</div><div class="stat-label">भारत के लिए बना</div></div> <div class="stat-card"><div class="stat-value">🇮🇳</div><div class="stat-label"><%= t('landing.madeInIndia') %></div></div>
</div> </div>
</div> </div>
</section> </section>
<section class="section" style="background:linear-gradient(135deg, var(--navy), var(--ashoka-blue)); color:var(--white); text-align:center;"> <section class="section" style="background:linear-gradient(135deg, var(--navy), var(--ashoka-blue)); color:var(--white); text-align:center;">
<div class="container"> <div class="container">
<h2 style="color:var(--white); font-size:1.5rem; margin-bottom:var(--space-sm);">आज ही शुरू करें — बिल्कुल मुफ्त!</h2> <h2 style="color:var(--white); font-size:1.5rem; margin-bottom:var(--space-sm);"><%= t('landing.ctaTitle') %></h2>
<p style="opacity:0.85; margin-bottom:var(--space-lg);">1000+ उपयोगकर्ताओं तक सभी सुविधाएं मुफ्त। कोई क्रेडिट कार्ड नहीं चाहिए।</p> <p style="opacity:0.85; margin-bottom:var(--space-lg);"><%= t('landing.ctaSub') %></p>
<a href="/register" class="btn btn-cta btn-lg">अभी पंजीकरण करें →</a> <a href="/register" class="btn btn-cta btn-lg"><%= t('auth.registerBtn') %> →</a>
</div> </div>
</section> </section>

View file

@ -0,0 +1,20 @@
<% var title = 'Leaderboard'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🏆 Leaderboard</h2>
<% if (myRank) { %><p style="color:var(--navy);font-weight:700;margin-bottom:var(--space-md)">Your Rank: #<%= myRank %></p><% } %>
<% leaderboard.forEach(l => { %>
<div class="card" style="padding:12px;margin-bottom:8px;display:flex;align-items:center;gap:12px;<%= l.user_id === user.id ? 'border:2px solid var(--navy)' : '' %>">
<div style="font-size:1.2rem;font-weight:700;width:30px;text-align:center"><%= l.rank <= 3 ? ['🥇','🥈','🥉'][l.rank-1] : '#'+l.rank %></div>
<div style="flex:1">
<strong><%= l.user?.name || l.user?.username || 'User' %></strong>
<div style="font-size:0.75rem;color:var(--gray-700)"><%= l.level.icon %> Level <%= l.level.level %> • <%= l.user?.role || '' %></div>
</div>
<div style="font-weight:700;color:var(--navy)"><%= l.xp %> XP</div>
</div>
<% }) %>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -1,89 +1,65 @@
<% var title = load.origin_city + ' → ' + load.destination_city; %> <% var title = load.origin_city + ' → ' + load.destination_city; %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)"> <section class="section" style="padding-top:var(--space-lg)">
<div class="container" style="max-width:600px"> <div class="container" style="max-width:600px">
<a href="/loadboard" style="font-size:0.8rem;color:var(--gray-700)">← लोड बोर्ड पर वापस</a> <a href="/loadboard" style="font-size:0.8rem;color:var(--gray-700)">← <%= t('actions.viewLoads') %></a>
<div class="card" style="margin-top:var(--space-md);<%= load.is_urgent ? 'border-left:4px solid var(--saffron)' : 'border-left:4px solid var(--ashoka-blue)' %>"> <div class="card" style="margin-top:var(--space-md);<%= load.is_urgent ? 'border-left:4px solid var(--saffron)' : 'border-left:4px solid var(--ashoka-blue)' %>">
<div style="display:flex;justify-content:space-between;align-items:start"> <div style="display:flex;justify-content:space-between;align-items:start">
<h2 style="font-size:1.2rem">📍 <%= load.origin_city %> → <%= load.destination_city %></h2> <h2 style="font-size:1.2rem">📍 <%= load.origin_city %> → <%= load.destination_city %></h2>
<span class="badge badge-<%= load.status === 'open' ? 'open' : load.status === 'booked' ? 'booked' : 'delivered' %>"><%= load.status %></span> <span class="badge badge-<%= load.status === 'open' ? 'open' : load.status === 'booked' ? 'booked' : 'delivered' %>"><%= load.status %></span>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md);margin-top:var(--space-md)"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md);margin-top:var(--space-md)">
<div><small style="color:var(--gray-700)">वज़न</small><br><strong><%= load.weight_tons %> टन</strong></div> <div><small style="color:var(--gray-700)">⚖️ <%= t('postLoad.weight') %></small><br><strong><%= load.weight_tons %> <%= t('common.tons') %></strong></div>
<div><small style="color:var(--gray-700)">ट्रक</small><br><strong><%= load.truck_type %></strong></div> <div><small style="color:var(--gray-700)">🚛 <%= t('common.truckType') %></small><br><strong><%= load.truck_type %></strong></div>
<div><small style="color:var(--gray-700)">पिकअप</small><br><strong><%= new Date(load.pickup_date).toLocaleDateString('hi-IN') %></strong></div> <div><small style="color:var(--gray-700)">📅 <%= t('postLoad.pickupDate') %></small><br><strong><%= new Date(load.pickup_date).toLocaleDateString('en-IN') %></strong></div>
<div><small style="color:var(--gray-700)">बजट</small><br><strong><%= load.budget ? '₹' + Number(load.budget).toLocaleString('en-IN') : 'बताया नहीं' %></strong></div> <div><small style="color:var(--gray-700)">💰 <%= t('postLoad.budget') %></small><br><strong><%= load.budget ? '₹' + Number(load.budget).toLocaleString('en-IN') : '' %></strong></div>
</div> </div>
<% if (load.material_type) { %><div style="margin-top:var(--space-sm)"><small style="color:var(--gray-700)">📦 <%= t('postLoad.material') %>:</small> <%= load.material_type %></div><% } %>
<% if (load.material_type) { %> <% if (load.description) { %><div style="margin-top:var(--space-sm)"><small style="color:var(--gray-700)">📝:</small> <%= load.description %></div><% } %>
<div style="margin-top:var(--space-sm)"><small style="color:var(--gray-700)">माल:</small> <%= load.material_type %></div>
<% } %>
<% if (load.description) { %>
<div style="margin-top:var(--space-sm)"><small style="color:var(--gray-700)">विवरण:</small> <%= load.description %></div>
<% } %>
<div style="margin-top:var(--space-md);padding-top:var(--space-md);border-top:1px solid var(--gray-200);font-size:0.8rem;color:var(--gray-700);display:flex;justify-content:space-between;align-items:center"> <div style="margin-top:var(--space-md);padding-top:var(--space-md);border-top:1px solid var(--gray-200);font-size:0.8rem;color:var(--gray-700);display:flex;justify-content:space-between;align-items:center">
<span>पोस्ट किया: <%= load.poster ? load.poster.name : 'Unknown' %> | <%= new Date(load.created_at).toLocaleDateString('hi-IN') %></span> <span><%= load.poster ? load.poster.name : '' %> | <%= new Date(load.created_at).toLocaleDateString('en-IN') %></span>
<a href="https://wa.me/?text=🚛 *लोड उपलब्ध*%0A📍 <%= load.origin_city %> → <%= load.destination_city %>%0A🏋 <%= load.weight_tons %> टन | <%= load.truck_type %><%= load.budget ? '%0A💰 ₹' + Number(load.budget).toLocaleString('en-IN') : '' %>%0A📅 <%= load.pickup_date %>%0A%0Ahttps://bharathtrucks.com/loadboard/<%= load.id %>" target="_blank" class="btn btn-sm" style="background:#25d366;color:#fff;padding:6px 12px;font-size:0.7rem">WhatsApp शेयर</a> <a href="/loadboard/whatsapp/<%= load.id %>" target="_blank" class="btn btn-sm" style="background:#25d366;color:#fff;padding:6px 12px;font-size:0.7rem">📱 WhatsApp</a>
</div> </div>
</div> </div>
<!-- Bid Form (drivers only, open loads) -->
<% if (user && user.role === 'driver' && load.status === 'open') { %> <% if (user && user.role === 'driver' && load.status === 'open') { %>
<div class="card" style="margin-top:var(--space-md)"> <div class="card" style="margin-top:var(--space-md)">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-md)"> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-md)">
<h3 style="font-size:1rem">🏷️ <%= myBid ? 'अपनी बोली अपडेट करें' : 'बोली लगाएं' %></h3> <h3 style="font-size:1rem">🏷️ <%= myBid ? t('loadDetail.updateBid') : t('actions.bid') %></h3>
<a href="/messages/<%= load.posted_by %>" class="btn btn-sm btn-outline" style="font-size:0.7rem">💬 शिपर से बात करें</a> <a href="/messages/<%= load.posted_by %>" class="btn btn-sm btn-outline" style="font-size:0.7rem">💬 Chat</a>
</div>
<form method="POST" action="/loadboard/<%= load.id %>/bid">
<div style="display:grid;grid-template-columns:1fr auto;gap:var(--space-sm);align-items:end">
<div class="form-group" style="margin:0">
<label class="form-label">आपकी कीमत (₹)</label>
<input type="number" name="amount" class="form-input" placeholder="42000" value="<%= myBid ? myBid.amount : '' %>" required>
</div>
<button type="submit" class="btn btn-success">बोली लगाएं</button>
</div>
<div class="form-group" style="margin-top:var(--space-sm)">
<input type="text" name="note" class="form-input" placeholder="कोई संदेश (वैकल्पिक)" value="<%= myBid ? myBid.note || '' : '' %>" style="padding:8px 12px;font-size:0.8rem">
</div>
</form>
</div> </div>
<form method="POST" action="/loadboard/<%= load.id %>/bid">
<div style="display:grid;grid-template-columns:1fr auto;gap:var(--space-sm);align-items:end">
<div class="form-group" style="margin:0">
<label class="form-label">💰 <%= t('loadDetail.yourPrice') %> (₹)</label>
<input type="number" name="amount" class="form-input" value="<%= myBid ? myBid.amount : '' %>" placeholder="<%= load.budget || '40000' %>" required>
</div>
<button type="submit" class="btn btn-cta"><%= t('actions.bid') %></button>
</div>
<div class="form-group" style="margin-top:var(--space-sm)">
<input type="text" name="note" class="form-input" placeholder="📝 Note" value="<%= myBid ? myBid.note || '' : '' %>">
</div>
</form>
</div>
<% } %> <% } %>
<!-- Bids List (visible to load owner) --> <% if (user && (user.role === 'shipper' || user.role === 'broker') && load.posted_by === user.id && bids && bids.length > 0) { %>
<% if (bids.length > 0 && user && (user.id === load.posted_by || user.role === 'admin')) { %> <div class="card" style="margin-top:var(--space-md)">
<div class="card" style="margin-top:var(--space-md)"> <h3 style="font-size:1rem;margin-bottom:var(--space-md)">🏷️ <%= t('loadDetail.bidsReceived') %> (<%= bids.length %>)</h3>
<h3 style="font-size:1rem;margin-bottom:var(--space-md)">📊 बोलियाँ (<%= bids.length %>)</h3> <% bids.forEach(bid => { %>
<% bids.forEach(bid => { %> <div style="display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid var(--gray-200)">
<div style="display:flex;justify-content:space-between;align-items:center;padding:10px 0;border-bottom:1px solid var(--gray-200)"> <div><strong><%= bid.driver ? bid.driver.name : 'Driver' %></strong><% if (bid.note) { %><div style="font-size:0.75rem;color:var(--gray-700)"><%= bid.note %></div><% } %></div>
<div> <div style="display:flex;align-items:center;gap:8px">
<strong style="font-size:0.9rem"><%= bid.driver ? bid.driver.name : 'Driver' %></strong> <strong style="color:var(--navy)">₹<%= Number(bid.amount).toLocaleString('en-IN') %></strong>
<div style="font-size:0.75rem;color:var(--gray-700)"><%= bid.driver ? bid.driver.username : '' %> <% if (bid.note) { %>| <%= bid.note %><% } %></div> <% if (load.status === 'open' && bid.status === 'pending') { %>
</div> <form method="POST" action="/loadboard/<%= load.id %>/accept-bid" style="margin:0"><input type="hidden" name="bid_id" value="<%= bid.id %>"><button class="btn btn-sm btn-cta">✓</button></form>
<div style="text-align:right"> <% } else { %><span class="badge badge-<%= bid.status %>"><%= bid.status %></span><% } %>
<strong style="color:var(--navy)">₹<%= Number(bid.amount).toLocaleString('en-IN') %></strong> </div>
<% if (load.status === 'open' && bid.status === 'pending') { %>
<form method="POST" action="/loadboard/<%= load.id %>/accept-bid" style="margin-top:4px">
<input type="hidden" name="bid_id" value="<%= bid.id %>">
<button type="submit" class="btn btn-success btn-sm" style="padding:4px 10px;font-size:0.7rem">स्वीकार</button>
</form>
<% } else { %>
<div><span class="badge badge-<%= bid.status === 'accepted' ? 'delivered' : 'cancelled' %>"><%= bid.status %></span></div>
<% } %>
</div>
</div>
<% }) %>
</div>
<% } else if (bids.length > 0) { %>
<div class="card" style="margin-top:var(--space-md)">
<p style="font-size:0.85rem;color:var(--gray-700)">🏷️ <%= bids.length %> बोली प्राप्त</p>
</div> </div>
<% }) %>
</div>
<% } %> <% } %>
</div> </div>
</section> </section>
<%- include('../partials/footer') %> <%- include('../partials/footer') %>

View file

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="hi">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= load.origin_city %> → <%= load.destination_city %> | BharathTrucks</title>
<meta property="og:title" content="🚛 Load: <%= load.origin_city %> → <%= load.destination_city %>">
<meta property="og:description" content="<%= load.weight_tons %> tons | ₹<%= (load.budget||0).toLocaleString('en-IN') %> | <%= load.truck_type %>">
<meta property="og:type" content="website">
<meta name="description" content="Freight load from <%= load.origin_city %> to <%= load.destination_city %> - <%= load.weight_tons %> tons">
<link rel="stylesheet" href="/css/govt-theme.css">
</head>
<body style="padding:20px">
<div class="container" style="max-width:500px;margin:0 auto">
<div class="card" style="padding:var(--space-lg)">
<div style="text-align:center;margin-bottom:var(--space-md)"><span style="font-size:2rem">🚛</span><h2>BharathTrucks</h2></div>
<h3 style="margin-bottom:var(--space-sm)">📍 <%= load.origin_city %> → <%= load.destination_city %></h3>
<div style="display:grid;gap:8px;font-size:0.9rem">
<div>⚖️ Weight: <strong><%= load.weight_tons %> tons</strong></div>
<div>🚛 Truck: <strong><%= load.truck_type %></strong></div>
<% if (load.budget) { %><div>💰 Budget: <strong>₹<%= Number(load.budget).toLocaleString('en-IN') %></strong></div><% } %>
<% if (load.pickup_date) { %><div>📅 Pickup: <strong><%= new Date(load.pickup_date).toLocaleDateString('en-IN') %></strong></div><% } %>
<% if (load.material_type) { %><div>📦 Material: <strong><%= load.material_type %></strong></div><% } %>
</div>
<a href="/loadboard/<%= load.id %>" class="btn btn-cta btn-block btn-lg" style="margin-top:var(--space-lg)">Bid on this Load →</a>
<a href="/register" class="btn btn-outline btn-block" style="margin-top:var(--space-sm)">Join Free</a>
</div>
</div>
</body>
</html>

View file

@ -1,45 +1,43 @@
<% var title = 'लोड बोर्ड'; %> <% var title = t('common.loadboard'); %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)"> <section class="section" style="padding-top:var(--space-lg)">
<div class="container"> <div class="container">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-md)"> <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-md)">
<h2 style="font-size:1.3rem">📋 लोड बोर्ड</h2> <h2 style="font-size:1.3rem">📋 <%= t('common.loadboard') %></h2>
<% if (user && (user.role === 'shipper' || user.role === 'broker')) { %> <% if (user && (user.role === 'shipper' || user.role === 'broker')) { %>
<a href="/loadboard/post" class="btn btn-cta btn-sm">+ लोड पोस्ट करें</a> <a href="/loadboard/post" class="btn btn-cta btn-sm">+ <%= t('actions.postLoad') %></a>
<% } %> <% } %>
</div> </div>
<!-- Filters -->
<form method="GET" action="/loadboard" class="card" style="padding:var(--space-md);margin-bottom:var(--space-md)"> <form method="GET" action="/loadboard" class="card" style="padding:var(--space-md);margin-bottom:var(--space-md)">
<div style="display:grid;grid-template-columns:1fr 1fr 1fr auto;gap:var(--space-sm);align-items:end"> <div style="display:grid;grid-template-columns:1fr 1fr 1fr auto;gap:var(--space-sm);align-items:end">
<div> <div>
<label class="form-label">कहाँ से</label> <label class="form-label"><%= t('common.from') %></label>
<input type="text" name="origin" class="form-input" placeholder="शहर" value="<%= filters.origin || '' %>" style="padding:8px 12px"> <input type="text" name="origin" class="form-input" placeholder="📍" value="<%= filters.origin || '' %>" style="padding:8px 12px">
</div> </div>
<div> <div>
<label class="form-label">कहाँ तक</label> <label class="form-label"><%= t('common.to') %></label>
<input type="text" name="destination" class="form-input" placeholder="शहर" value="<%= filters.destination || '' %>" style="padding:8px 12px"> <input type="text" name="destination" class="form-input" placeholder="📍" value="<%= filters.destination || '' %>" style="padding:8px 12px">
</div> </div>
<div> <div>
<label class="form-label">ट्रक प्रकार</label> <label class="form-label"><%= t('common.truckType') %></label>
<select name="truck_type" class="form-input form-select" style="padding:8px 12px"> <select name="truck_type" class="form-input form-select" style="padding:8px 12px">
<option value="all">सभी</option> <option value="all"><%= t('common.all') %></option>
<% truckTypes.forEach(t => { %> <% truckTypes.forEach(t_type => { %>
<option value="<%= t %>" <%= filters.truck_type === t ? 'selected' : '' %>><%= t %></option> <option value="<%= t_type %>" <%= filters.truck_type === t_type ? 'selected' : '' %>><%= t_type %></option>
<% }) %> <% }) %>
</select> </select>
</div> </div>
<button type="submit" class="btn btn-primary btn-sm">खोजें</button> <button type="submit" class="btn btn-primary btn-sm">🔍 <%= t('actions.search') %></button>
</div> </div>
</form> </form>
<!-- Load List -->
<% if (loads.length === 0) { %> <% if (loads.length === 0) { %>
<div class="card text-center" style="padding:var(--space-2xl)"> <div class="card text-center" style="padding:var(--space-2xl)">
<p style="font-size:1.2rem">📭</p> <p style="font-size:1.2rem">📭</p>
<p style="color:var(--gray-700)">कोई लोड उपलब्ध नहीं</p> <p style="color:var(--gray-700)"><%= t('common.noLoads') %></p>
</div> </div>
<% } else { %> <% } else { %>
<div style="display:grid;gap:var(--space-md)"> <div style="display:grid;gap:var(--space-md)">
@ -49,12 +47,12 @@
<div> <div>
<strong style="font-size:0.95rem">📍 <%= load.origin_city %> → <%= load.destination_city %></strong> <strong style="font-size:0.95rem">📍 <%= load.origin_city %> → <%= load.destination_city %></strong>
<div style="font-size:0.8rem;color:var(--gray-700);margin-top:4px"> <div style="font-size:0.8rem;color:var(--gray-700);margin-top:4px">
🚛 <%= load.weight_tons %> टन | <%= load.truck_type %> 🚛 <%= load.weight_tons %> <%= t('common.tons') %> | <%= load.truck_type %>
<% if (load.material_type) { %> | <%= load.material_type %><% } %> <% if (load.material_type) { %> | <%= load.material_type %><% } %>
</div> </div>
<div style="font-size:0.8rem;color:var(--gray-700);margin-top:2px"> <div style="font-size:0.8rem;color:var(--gray-700);margin-top:2px">
📅 <%= new Date(load.pickup_date).toLocaleDateString('hi-IN') %> 📅 <%= new Date(load.pickup_date).toLocaleDateString('hi-IN') %>
<% if (load.bid_count > 0) { %> | 🏷️ <%= load.bid_count %> बोली<% } %> <% if (load.bid_count > 0) { %> | 🏷️ <%= load.bid_count %> <%= t('common.bids') %><% } %>
</div> </div>
</div> </div>
<div style="text-align:right"> <div style="text-align:right">
@ -62,7 +60,7 @@
<div style="font-size:1rem;font-weight:700;color:var(--navy)">₹<%= Number(load.budget).toLocaleString('en-IN') %></div> <div style="font-size:1rem;font-weight:700;color:var(--navy)">₹<%= Number(load.budget).toLocaleString('en-IN') %></div>
<% } %> <% } %>
<% if (load.is_urgent) { %> <% if (load.is_urgent) { %>
<span class="badge badge-booked">अर्जेंट</span> <span class="badge badge-booked"><%= t('common.urgent') %></span>
<% } %> <% } %>
</div> </div>
</div> </div>

View file

@ -1,12 +1,12 @@
<% var title = 'लॉगिन'; %> <% var title = t('actions.login'); %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section"> <section class="section">
<div class="container" style="max-width:400px"> <div class="container" style="max-width:400px">
<div class="card"> <div class="card">
<h2 class="text-center" style="margin-bottom:4px">लॉगिन | Login</h2> <h2 class="text-center" style="margin-bottom:4px"><%= t('auth.loginTitle') %></h2>
<p class="text-center" style="color:var(--gray-700);font-size:0.8rem;margin-bottom:var(--space-lg)">अपना यूज़रनेम और पासवर्ड दर्ज करें</p> <p class="text-center" style="color:var(--gray-700);font-size:0.8rem;margin-bottom:var(--space-lg)"><%= t('auth.loginSubtitle') %></p>
<% if (error) { %> <% if (error) { %>
<div class="alert-error"><%= error %></div> <div class="alert-error"><%= error %></div>
@ -14,18 +14,18 @@
<form method="POST" action="/login"> <form method="POST" action="/login">
<div class="form-group"> <div class="form-group">
<label class="form-label">यूज़रनेम (ड्राइवर: गाड़ी नंबर)</label> <label class="form-label">👤 <%= t('auth.username') %></label>
<input type="text" name="username" class="form-input" placeholder="MH31AB1234 या अपना यूज़रनेम" required autofocus> <input type="text" name="username" class="form-input" placeholder="MH31AB1234" required autofocus>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">पासवर्ड</label> <label class="form-label">🔒 <%= t('auth.password') %></label>
<input type="password" name="password" class="form-input" placeholder="••••" required> <input type="password" name="password" class="form-input" placeholder="••••" required>
</div> </div>
<button type="submit" class="btn btn-primary btn-block btn-lg">लॉगिन करें →</button> <button type="submit" class="btn btn-primary btn-block btn-lg"><%= t('actions.login') %> →</button>
</form> </form>
<p class="text-center" style="margin-top:var(--space-lg);font-size:0.8rem"> <p class="text-center" style="margin-top:var(--space-lg);font-size:0.8rem">
नया खाता? <a href="/register">पंजीकरण करें</a> <%= t('auth.noAccount') %> <a href="/register"><%= t('actions.register') %></a>
</p> </p>
</div> </div>
</div> </div>

View file

@ -0,0 +1,37 @@
<% var title = 'Maintenance'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🔧 Vehicle Reminders</h2>
<div class="stats-grid" style="margin-bottom:var(--space-md)">
<div class="stat-card"><div class="stat-value"><%= stats.total %></div><div class="stat-label">Total</div></div>
<div class="stat-card"><div class="stat-value" style="color:red"><%= stats.expired %></div><div class="stat-label">🔴 Expired</div></div>
<div class="stat-card"><div class="stat-value" style="color:orange"><%= stats.expiring %></div><div class="stat-label">🟡 Expiring</div></div>
</div>
<% reminders.forEach(r => { %>
<div class="card card-accent" style="margin-bottom:var(--space-sm);border-left-color:<%= r.urgency==='expired'||r.urgency==='critical'?'red':r.urgency==='warning'?'orange':'var(--green)' %>">
<div style="display:flex;justify-content:space-between;align-items:center">
<div>
<strong><%= r.doc_type.toUpperCase() %></strong> — <%= r.vehicle_number %>
<div style="font-size:0.8rem;color:var(--gray-700)">📅 <%= r.expiry_date %> | <span style="color:<%= r.days_left<0?'red':'orange' %>"><%= r.days_left < 0 ? Math.abs(r.days_left) + ' days overdue' : r.days_left + ' days left' %></span></div>
</div>
<form method="POST" action="/maintenance/delete/<%= r.id %>" style="margin:0"><button class="btn btn-sm" style="color:red">✕</button></form>
</div>
</div>
<% }) %>
<form method="POST" action="/maintenance/add" class="card" style="padding:var(--space-md);margin-top:var(--space-lg)">
<h4 style="margin-bottom:var(--space-sm)"> Add Reminder</h4>
<div style="display:grid;gap:var(--space-sm)">
<input type="text" name="vehicle_number" class="form-input" placeholder="🚛 Vehicle Number" required>
<select name="doc_type" class="form-input form-select" required>
<option value="insurance">🛡️ Insurance</option><option value="fitness">🏋️ Fitness</option>
<option value="permit">📄 Permit</option><option value="puc">💨 PUC</option><option value="service">🔧 Service</option>
</select>
<input type="date" name="expiry_date" class="form-input" required>
<button type="submit" class="btn btn-primary btn-block">Save</button>
</div>
</form>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -1,34 +1,19 @@
<% var title = 'संदेश'; %> <% var title = t('nav.messages'); %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)"> <section class="section" style="padding-top:var(--space-lg)">
<div class="container" style="max-width:500px"> <div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">💬 संदेश</h2> <h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">💬 <%= t('nav.messages') %></h2>
<% if (conversations.length === 0) { %> <% if (conversations.length === 0) { %>
<div class="card text-center" style="padding:var(--space-2xl)"> <div class="card text-center" style="padding:var(--space-2xl)"><p><%= t('messages.noMessages') %></p></div>
<p>कोई संदेश नहीं</p> <% } else { conversations.forEach(c => { %>
<a href="/messages/<%= c.other_user_id %>" class="card" style="display:block;text-decoration:none;color:inherit;padding:14px;margin-bottom:8px">
<div style="display:flex;justify-content:space-between;align-items:center">
<div><strong><%= c.other_user_name || 'User' %></strong><div style="font-size:0.8rem;color:var(--gray-700)"><%= c.last_message || '' %></div></div>
<span style="font-size:0.7rem;color:var(--gray-500)"><%= c.last_at ? new Date(c.last_at).toLocaleDateString('en-IN') : '' %></span>
</div> </div>
<% } else { %> </a>
<div style="display:grid;gap:2px"> <% }) } %>
<% conversations.forEach(c => { %>
<a href="/messages/<%= c.lastMsg.sender_id === user.id ? c.lastMsg.receiver_id : c.lastMsg.sender_id %>" class="card" style="text-decoration:none;color:inherit;padding:12px var(--space-md)">
<div style="display:flex;justify-content:space-between;align-items:center">
<div>
<strong style="font-size:0.9rem"><%= c.user ? c.user.name : 'User' %></strong>
<div style="font-size:0.75rem;color:var(--gray-700);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:200px"><%= c.lastMsg.content %></div>
</div>
<div style="text-align:right">
<% if (c.unread > 0) { %><span class="badge badge-booked"><%= c.unread %></span><% } %>
<div style="font-size:0.65rem;color:var(--gray-500)"><%= new Date(c.lastMsg.created_at).toLocaleDateString('hi-IN') %></div>
</div>
</div>
</a>
<% }) %>
</div>
<% } %>
</div> </div>
</section> </section>
<%- include('../partials/footer') %> <%- include('../partials/footer') %>

View file

@ -0,0 +1,34 @@
<% var title = 'More'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">📱 All Features</h2>
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:var(--space-md)">
<a href="/games" class="icon-action-btn"><span class="icon-action-emoji">🎮</span><span class="icon-action-label">Games</span></a>
<a href="/news" class="icon-action-btn"><span class="icon-action-emoji">📰</span><span class="icon-action-label">News</span></a>
<a href="/documents" class="icon-action-btn"><span class="icon-action-emoji">📄</span><span class="icon-action-label">Docs</span></a>
<a href="/bank" class="icon-action-btn"><span class="icon-action-emoji">🏦</span><span class="icon-action-label">Bank</span></a>
<a href="/reports" class="icon-action-btn"><span class="icon-action-emoji">📊</span><span class="icon-action-label">Reports</span></a>
<a href="/search" class="icon-action-btn"><span class="icon-action-emoji">🔍</span><span class="icon-action-label">Search</span></a>
<a href="/classifieds" class="icon-action-btn"><span class="icon-action-emoji">🛒</span><span class="icon-action-label">Buy/Sell</span></a>
<a href="/fleet" class="icon-action-btn"><span class="icon-action-emoji">🚛</span><span class="icon-action-label">Fleet</span></a>
<a href="/invoice" class="icon-action-btn"><span class="icon-action-emoji">🧾</span><span class="icon-action-label">Invoice</span></a>
<a href="/rates" class="icon-action-btn"><span class="icon-action-emoji">💹</span><span class="icon-action-label">Rates</span></a>
<a href="/referral" class="icon-action-btn"><span class="icon-action-emoji">🤝</span><span class="icon-action-label">Referral</span></a>
<a href="/gamification" class="icon-action-btn"><span class="icon-action-emoji">🏆</span><span class="icon-action-label">Level</span></a>
<a href="/leaderboard" class="icon-action-btn"><span class="icon-action-emoji">🥇</span><span class="icon-action-label">Rank</span></a>
<a href="/challenges" class="icon-action-btn"><span class="icon-action-emoji">🎯</span><span class="icon-action-label">Daily</span></a>
<a href="/feed" class="icon-action-btn"><span class="icon-action-emoji">📰</span><span class="icon-action-label">Feed</span></a>
<% if (user.role === 'driver') { %>
<a href="/driver/ledger" class="icon-action-btn"><span class="icon-action-emoji">📒</span><span class="icon-action-label">Ledger</span></a>
<a href="/trip-planner" class="icon-action-btn"><span class="icon-action-emoji">🧮</span><span class="icon-action-label">Trip Cost</span></a>
<a href="/returnload" class="icon-action-btn"><span class="icon-action-emoji">🔄</span><span class="icon-action-label">Return</span></a>
<a href="/safety" class="icon-action-btn"><span class="icon-action-emoji">🛡️</span><span class="icon-action-label">Safety</span></a>
<a href="/maintenance" class="icon-action-btn"><span class="icon-action-emoji">🔧</span><span class="icon-action-label">Reminders</span></a>
<a href="/fastag" class="icon-action-btn"><span class="icon-action-emoji">🏷️</span><span class="icon-action-label">FASTag</span></a>
<% } %>
</div>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,23 @@
<% var title = 'News'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">📰 Trucker News</h2>
<% if (news.length === 0) { %>
<div class="card text-center" style="padding:var(--space-2xl)"><p>No news yet. Check back later!</p></div>
<% } else { news.forEach(n => { %>
<div class="card" style="padding:var(--space-md);margin-bottom:var(--space-sm)">
<div style="display:flex;gap:8px;align-items:start">
<span style="font-size:1.3rem"><%= n.category==='diesel'?'⛽':n.category==='toll'?'🚧':n.category==='alert'?'⚠️':'📰' %></span>
<div>
<strong><%= n.title %></strong>
<% if (n.content) { %><p style="font-size:0.85rem;color:var(--gray-700);margin-top:4px"><%= n.content %></p><% } %>
<div style="font-size:0.7rem;color:var(--gray-500);margin-top:4px"><%= new Date(n.created_at).toLocaleDateString('en-IN') %></div>
</div>
</div>
</div>
<% }) } %>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,22 @@
<% var title = 'Notifications'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🔔 Notifications</h2>
<% if (notifications.length === 0) { %>
<div class="card text-center" style="padding:var(--space-2xl)"><p>✅ All clear! No pending actions.</p></div>
<% } else { notifications.forEach(n => { %>
<a href="<%= n.url || '#' %>" class="card card-accent" style="display:block;text-decoration:none;color:inherit;margin-bottom:var(--space-sm);border-left-color:<%= n.priority==='high'?'red':n.priority==='medium'?'orange':'var(--gray-300)' %>">
<div style="display:flex;gap:var(--space-sm);align-items:start">
<span style="font-size:1.3rem"><%= n.icon %></span>
<div>
<strong style="font-size:0.9rem"><%= n.title %></strong>
<div style="font-size:0.8rem;color:var(--gray-700)"><%= n.subtitle || '' %></div>
</div>
</div>
</a>
<% }) } %>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,18 @@
<% var title = 'Welcome'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container text-center">
<div style="font-size:3rem;margin-bottom:var(--space-sm)">🎮</div>
<h2 style="font-size:1.3rem">Welcome! You earned <%= xp %> XP</h2>
<p style="color:var(--gray-700);margin-bottom:var(--space-lg)">Complete steps to level up!</p>
<div style="display:grid;gap:var(--space-sm);text-align:left">
<a href="/profile" class="card card-accent" style="padding:14px;text-decoration:none;color:inherit"><span style="font-size:1.2rem">👤</span> Complete your profile (+30 XP)</a>
<a href="/loadboard" class="card card-accent" style="padding:14px;text-decoration:none;color:inherit"><span style="font-size:1.2rem">📋</span> Browse loads (+10 XP)</a>
<a href="/safety" class="card card-accent" style="padding:14px;text-decoration:none;color:inherit"><span style="font-size:1.2rem">🛡️</span> Add safety contact (+20 XP)</a>
<a href="/referral" class="card card-accent" style="padding:14px;text-decoration:none;color:inherit"><span style="font-size:1.2rem">🤝</span> Invite a friend (+40 XP)</a>
</div>
<a href="/<%= user.role %>" class="btn btn-primary btn-block" style="margin-top:var(--space-lg)">Go to Dashboard →</a>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -1,75 +1,32 @@
<% var title = 'लोड पोस्ट करें'; %> <% var title = t('actions.postLoad'); %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)"> <section class="section" style="padding-top:var(--space-lg)">
<div class="container" style="max-width:500px"> <div class="container" style="max-width:500px">
<div class="card"> <div class="card">
<h2 style="font-size:1.2rem;margin-bottom:var(--space-md)">📦 नया लोड पोस्ट करें</h2> <h2 style="font-size:1.2rem;margin-bottom:var(--space-md)">📦 <%= t('actions.postLoad') %></h2>
<% if (error) { %><div class="alert-error"><%= error %></div><% } %>
<% if (error) { %>
<div class="alert-error"><%= error %></div>
<% } %>
<form method="POST" action="/loadboard/post"> <form method="POST" action="/loadboard/post">
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)">
<div class="form-group"> <div class="form-group"><label class="form-label">📍 <%= t('common.from') %> *</label><input type="text" name="origin_city" class="form-input" placeholder="Mumbai" required></div>
<label class="form-label">कहाँ से / Origin *</label> <div class="form-group"><label class="form-label">📍 <%= t('common.to') %> *</label><input type="text" name="destination_city" class="form-input" placeholder="Delhi" required></div>
<input type="text" name="origin_city" class="form-input" placeholder="मुंबई" required>
</div>
<div class="form-group">
<label class="form-label">कहाँ तक / Destination *</label>
<input type="text" name="destination_city" class="form-input" placeholder="दिल्ली" required>
</div>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)">
<div class="form-group"> <div class="form-group"><label class="form-label">⚖️ <%= t('postLoad.weight') %> *</label><input type="number" name="weight_tons" class="form-input" placeholder="20" step="0.5" required></div>
<label class="form-label">वज़न (टन) *</label> <div class="form-group"><label class="form-label">🚛 <%= t('common.truckType') %> *</label>
<input type="number" name="weight_tons" class="form-input" placeholder="20" step="0.5" required> <select name="truck_type" class="form-input form-select" required><option value=""><%= t('postLoad.select') %></option><% truckTypes.forEach(tt => { %><option value="<%= tt %>"><%= tt %></option><% }) %></select>
</div>
<div class="form-group">
<label class="form-label">ट्रक प्रकार *</label>
<select name="truck_type" class="form-input form-select" required>
<option value="">चुनें</option>
<% truckTypes.forEach(t => { %>
<option value="<%= t %>"><%= t %></option>
<% }) %>
</select>
</div> </div>
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)">
<div class="form-group"> <div class="form-group"><label class="form-label">📦 <%= t('postLoad.material') %></label><input type="text" name="material_type" class="form-input" placeholder="Cement, Steel..."></div>
<label class="form-label">माल का प्रकार</label> <div class="form-group"><label class="form-label">💰 <%= t('postLoad.budget') %></label><input type="number" name="budget" class="form-input" placeholder="45000"></div>
<input type="text" name="material_type" class="form-input" placeholder="सीमेंट, स्टील...">
</div>
<div class="form-group">
<label class="form-label">बजट (₹)</label>
<input type="number" name="budget" class="form-input" placeholder="45000">
</div>
</div> </div>
<div class="form-group"><label class="form-label">📅 <%= t('postLoad.pickupDate') %> *</label><input type="date" name="pickup_date" class="form-input" required></div>
<div class="form-group"> <div class="form-group"><label class="form-label">📝 <%= t('postLoad.notes') %></label><textarea name="description" class="form-input" rows="2" placeholder=""></textarea></div>
<label class="form-label">पिकअप तारीख *</label> <div class="form-group"><label style="display:flex;align-items:center;gap:8px;font-size:0.85rem;cursor:pointer"><input type="checkbox" name="is_urgent"> 🔴 <%= t('common.urgent') %></label></div>
<input type="date" name="pickup_date" class="form-input" required> <button type="submit" class="btn btn-primary btn-block btn-lg"><%= t('actions.postLoad') %> →</button>
</div>
<div class="form-group">
<label class="form-label">विवरण / Notes</label>
<textarea name="description" class="form-input" rows="2" placeholder="अतिरिक्त जानकारी..."></textarea>
</div>
<div class="form-group">
<label style="display:flex;align-items:center;gap:8px;font-size:0.85rem;cursor:pointer">
<input type="checkbox" name="is_urgent"> 🔴 अर्जेंट लोड
</label>
</div>
<button type="submit" class="btn btn-primary btn-block btn-lg">लोड पोस्ट करें →</button>
</form> </form>
</div> </div>
</div> </div>
</section> </section>
<%- include('../partials/footer') %> <%- include('../partials/footer') %>

View file

@ -1,51 +1,29 @@
<% var title = 'मेरी प्रोफ़ाइल'; %> <% var title = t('nav.profile'); %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)"> <section class="section" style="padding-top:var(--space-lg)">
<div class="container" style="max-width:500px"> <div class="container" style="max-width:500px">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">👤 मेरी प्रोफ़ाइल</h2> <h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">👤 <%= t('nav.profile') %></h2>
<% if (success) { %><div style="background:#e8f5e9;color:green;border-radius:var(--radius-sm);padding:10px 14px;font-size:0.8rem;margin-bottom:var(--space-md)">✓ <%= t('profile.updated') %></div><% } %>
<% if (success) { %>
<div style="background:#e8f5e9;color:var(--green);border:1px solid #c8e6c9;border-radius:var(--radius-sm);padding:10px 14px;font-size:0.8rem;margin-bottom:var(--space-md)">✓ प्रोफ़ाइल अपडेट हो गई</div>
<% } %>
<div class="card"> <div class="card">
<div style="display:flex;align-items:center;gap:var(--space-md);margin-bottom:var(--space-lg)"> <div style="display:flex;align-items:center;gap:var(--space-md);margin-bottom:var(--space-lg)">
<div style="width:50px;height:50px;background:var(--navy);color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.2rem;font-weight:700"><%= profile.name ? profile.name.charAt(0).toUpperCase() : '?' %></div> <div style="width:50px;height:50px;background:var(--navy);color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:1.2rem;font-weight:700"><%= profile.name ? profile.name.charAt(0).toUpperCase() : '?' %></div>
<div> <div><strong><%= profile.name %></strong><div style="font-size:0.8rem;color:var(--gray-700)">@<%= profile.username %> | <span class="badge badge-open"><%= profile.role %></span></div></div>
<strong><%= profile.name %></strong>
<div style="font-size:0.8rem;color:var(--gray-700)">@<%= profile.username %> | <span class="badge badge-open"><%= profile.role %></span></div>
</div>
</div> </div>
<form method="POST" action="/profile"> <form method="POST" action="/profile">
<div class="form-group"> <div class="form-group"><label class="form-label">👤 <%= t('profile.name') %></label><input type="text" name="name" class="form-input" value="<%= profile.name %>" required></div>
<label class="form-label">नाम / Name</label> <div class="form-group"><label class="form-label">📱 <%= t('profile.phone') %></label><input type="tel" name="phone" class="form-input" value="<%= profile.phone || '' %>" placeholder="9876543210"></div>
<input type="text" name="name" class="form-input" value="<%= profile.name %>" required>
</div>
<div class="form-group">
<label class="form-label">फोन नंबर</label>
<input type="tel" name="phone" class="form-input" value="<%= profile.phone || '' %>" placeholder="9876543210">
</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)">
<div class="form-group"> <div class="form-group"><label class="form-label">🏙️ <%= t('profile.city') %></label><input type="text" name="city" class="form-input" value="<%= profile.city || '' %>"></div>
<label class="form-label">शहर</label> <div class="form-group"><label class="form-label">🗺️ <%= t('profile.state') %></label><input type="text" name="state" class="form-input" value="<%= profile.state || '' %>"></div>
<input type="text" name="city" class="form-input" value="<%= profile.city || '' %>">
</div>
<div class="form-group">
<label class="form-label">राज्य</label>
<input type="text" name="state" class="form-input" value="<%= profile.state || '' %>">
</div>
</div> </div>
<button type="submit" class="btn btn-primary btn-block">प्रोफ़ाइल अपडेट करें</button> <button type="submit" class="btn btn-primary btn-block"><%= t('profile.update') %></button>
</form> </form>
<div style="margin-top:var(--space-lg);padding-top:var(--space-md);border-top:1px solid var(--gray-200)"> <div style="margin-top:var(--space-lg);padding-top:var(--space-md);border-top:1px solid var(--gray-200)">
<a href="/logout" class="btn btn-outline btn-block" style="color:var(--red);border-color:var(--red)">लॉगआउट</a> <a href="/gamification" class="btn btn-outline btn-block" style="margin-bottom:8px">🏆 <%= t('profile.myLevel') %></a>
<a href="/logout" class="btn btn-outline btn-block" style="color:red;border-color:red"><%= t('actions.logout') %></a>
</div> </div>
</div> </div>
</div> </div>
</section> </section>
<%- include('../partials/footer') %> <%- include('../partials/footer') %>

View file

@ -0,0 +1,29 @@
<% var title = 'Rate Check'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">📊 Rate Intelligence</h2>
<form method="GET" action="/rates" class="card" style="padding:var(--space-md);margin-bottom:var(--space-md)">
<div style="display:grid;grid-template-columns:1fr 1fr auto;gap:var(--space-sm);align-items:end">
<div><label class="form-label">📍 From</label><input type="text" name="origin" class="form-input" value="<%= origin %>" placeholder="City" required></div>
<div><label class="form-label">📍 To</label><input type="text" name="destination" class="form-input" value="<%= destination %>" placeholder="City" required></div>
<button type="submit" class="btn btn-primary btn-sm">🔍</button>
</div>
</form>
<% if (rates) { %>
<div class="card" style="padding:var(--space-md)">
<h3 style="font-size:1rem;margin-bottom:var(--space-sm)"><%= rates.origin %> → <%= rates.destination %></h3>
<p style="font-size:0.8rem;color:var(--gray-700);margin-bottom:var(--space-md)">Based on <%= rates.count %> recent loads</p>
<div class="stats-grid">
<div class="stat-card"><div class="stat-value">₹<%= rates.avg.toLocaleString('en-IN') %></div><div class="stat-label">Average</div></div>
<div class="stat-card"><div class="stat-value">₹<%= rates.min.toLocaleString('en-IN') %></div><div class="stat-label">Min</div></div>
<div class="stat-card"><div class="stat-value">₹<%= rates.max.toLocaleString('en-IN') %></div><div class="stat-label">Max</div></div>
</div>
</div>
<% } else if (origin && destination) { %>
<div class="card text-center" style="padding:var(--space-xl)"><p>No rate data for this route yet.</p></div>
<% } %>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,26 @@
<% var title = 'Referral'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🤝 Invite & Earn</h2>
<div class="card" style="padding:var(--space-lg);text-align:center;margin-bottom:var(--space-md)">
<p style="font-size:0.85rem;color:var(--gray-700)">Your Referral Code:</p>
<div style="font-size:1.5rem;font-weight:700;color:var(--navy);margin:8px 0"><%= code %></div>
<a href="https://wa.me/?text=<%= encodeURIComponent(shareMsg) %>" target="_blank" class="btn btn-block" style="background:#25d366;color:#fff">📱 Share on WhatsApp</a>
</div>
<div class="stats-grid" style="margin-bottom:var(--space-md)">
<div class="stat-card"><div class="stat-value"><%= stats.total %></div><div class="stat-label">Invited</div></div>
<div class="stat-card"><div class="stat-value"><%= stats.joined %></div><div class="stat-label">Joined</div></div>
</div>
<% if (referrals.length > 0) { %>
<h3 style="font-size:1rem;margin-bottom:var(--space-sm)">Recent Referrals</h3>
<% referrals.slice(0,10).forEach(r => { %>
<div class="card" style="padding:10px;margin-bottom:6px;display:flex;justify-content:space-between">
<span><%= r.referral_code %></span>
<span class="badge badge-<%= r.status === 'joined' ? 'open' : 'transit' %>"><%= r.status %></span>
</div>
<% }) } %>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -1,12 +1,12 @@
<% var title = 'पंजीकरण'; %> <% var title = t('actions.register'); %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section"> <section class="section">
<div class="container" style="max-width:440px"> <div class="container" style="max-width:440px">
<div class="card"> <div class="card">
<h2 class="text-center" style="margin-bottom:4px">पंजीकरण | Register</h2> <h2 class="text-center" style="margin-bottom:4px"><%= t('auth.registerTitle') %></h2>
<p class="text-center" style="color:var(--gray-700);font-size:0.8rem;margin-bottom:var(--space-lg)">मुफ्त खाता बनाएं</p> <p class="text-center" style="color:var(--gray-700);font-size:0.8rem;margin-bottom:var(--space-lg)"><%= t('auth.registerSubtitle') %></p>
<% if (error) { %> <% if (error) { %>
<div class="alert-error"><%= error %></div> <div class="alert-error"><%= error %></div>
@ -14,55 +14,55 @@
<form method="POST" action="/register" id="registerForm"> <form method="POST" action="/register" id="registerForm">
<div class="form-group"> <div class="form-group">
<label class="form-label">आप कौन हैं? / Your Role *</label> <label class="form-label"><%= t('auth.yourRole') %> *</label>
<div class="role-select-grid"> <div class="role-select-grid">
<label class="role-option"> <label class="role-option">
<input type="radio" name="role" value="driver" <%= role === 'driver' ? 'checked' : '' %> required> <input type="radio" name="role" value="driver" <%= role === 'driver' ? 'checked' : '' %> required>
<div class="role-option-card"><span class="role-icon">🚛</span><span>ड्राइवर</span></div> <div class="role-option-card"><span class="role-icon">🚛</span><span><%= t('auth.driver') %></span></div>
</label> </label>
<label class="role-option"> <label class="role-option">
<input type="radio" name="role" value="shipper" <%= role === 'shipper' ? 'checked' : '' %>> <input type="radio" name="role" value="shipper" <%= role === 'shipper' ? 'checked' : '' %>>
<div class="role-option-card"><span class="role-icon">📦</span><span>शिपर</span></div> <div class="role-option-card"><span class="role-icon">📦</span><span><%= t('auth.shipper') %></span></div>
</label> </label>
<label class="role-option"> <label class="role-option">
<input type="radio" name="role" value="broker" <%= role === 'broker' ? 'checked' : '' %>> <input type="radio" name="role" value="broker" <%= role === 'broker' ? 'checked' : '' %>>
<div class="role-option-card"><span class="role-icon">🤝</span><span>ब्रोकर</span></div> <div class="role-option-card"><span class="role-icon">🤝</span><span><%= t('auth.broker') %></span></div>
</label> </label>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">पूरा नाम / Full Name *</label> <label class="form-label">👤 <%= t('auth.fullName') %> *</label>
<input type="text" name="name" class="form-input" placeholder="अपना नाम" required> <input type="text" name="name" class="form-input" placeholder="<%= t('auth.fullName') %>" required>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label" id="usernameLabel">यूज़रनेम *</label> <label class="form-label" id="usernameLabel">📝 <%= t('auth.username') %> *</label>
<input type="text" name="username" class="form-input" id="usernameInput" placeholder="यूज़रनेम चुनें" required> <input type="text" name="username" class="form-input" id="usernameInput" placeholder="<%= t('auth.username') %>" required>
<small id="usernameHint" style="color:var(--gray-700);font-size:0.7rem"></small> <small id="usernameHint" style="color:var(--gray-700);font-size:0.7rem"></small>
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">फोन नंबर (वैकल्पिक)</label> <label class="form-label">📱 <%= t('auth.phone') %></label>
<input type="tel" name="phone" class="form-input" placeholder="9876543210" maxlength="10"> <input type="tel" name="phone" class="form-input" placeholder="9876543210" maxlength="10">
</div> </div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)"> <div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)">
<div class="form-group"> <div class="form-group">
<label class="form-label">पासवर्ड *</label> <label class="form-label">🔒 <%= t('auth.password') %> *</label>
<input type="password" name="password" class="form-input" placeholder="••••" required minlength="4"> <input type="password" name="password" class="form-input" placeholder="••••" required minlength="4">
</div> </div>
<div class="form-group"> <div class="form-group">
<label class="form-label">पासवर्ड पुष्टि *</label> <label class="form-label">🔒 <%= t('auth.confirmPassword') %> *</label>
<input type="password" name="password_confirm" class="form-input" placeholder="••••" required minlength="4"> <input type="password" name="password_confirm" class="form-input" placeholder="••••" required minlength="4">
</div> </div>
</div> </div>
<button type="submit" class="btn btn-cta btn-block btn-lg">मुफ्त पंजीकरण करें →</button> <button type="submit" class="btn btn-cta btn-block btn-lg"><%= t('auth.registerBtn') %> →</button>
</form> </form>
<p class="text-center" style="margin-top:var(--space-lg);font-size:0.8rem"> <p class="text-center" style="margin-top:var(--space-lg);font-size:0.8rem">
पहले से खाता है? <a href="/login">लॉगिन करें</a> <%= t('auth.hasAccount') %> <a href="/login"><%= t('actions.login') %></a>
</p> </p>
</div> </div>
</div> </div>
@ -75,17 +75,16 @@ document.querySelectorAll('input[name="role"]').forEach(r => {
const input = document.getElementById('usernameInput'); const input = document.getElementById('usernameInput');
const hint = document.getElementById('usernameHint'); const hint = document.getElementById('usernameHint');
if (this.value === 'driver') { if (this.value === 'driver') {
label.textContent = 'गाड़ी नंबर / Vehicle Number *'; label.innerHTML = '🚛 <%= t("auth.vehicleNumber") %> *';
input.placeholder = 'MH31AB1234'; input.placeholder = 'MH31AB1234';
hint.textContent = 'आपका गाड़ी नंबर ही आपका यूज़रनेम होगा'; hint.textContent = '<%= t("auth.vehicleHint") %>';
} else { } else {
label.textContent = 'यूज़रनेम *'; label.innerHTML = '📝 <%= t("auth.username") %> *';
input.placeholder = 'अपना यूज़रनेम चुनें'; input.placeholder = '<%= t("auth.username") %>';
hint.textContent = ''; hint.textContent = '';
} }
}); });
}); });
// Trigger on load if role pre-selected
const checked = document.querySelector('input[name="role"]:checked'); const checked = document.querySelector('input[name="role"]:checked');
if (checked) checked.dispatchEvent(new Event('change')); if (checked) checked.dispatchEvent(new Event('change'));
</script> </script>

View file

@ -0,0 +1,22 @@
<% var title = 'Reports'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:var(--space-md)">
<h2 style="font-size:1.3rem">📊 Reports</h2>
<a href="/reports/export" class="btn btn-sm btn-outline">📥 CSV Export</a>
</div>
<div class="stats-grid" style="margin-bottom:var(--space-md)">
<div class="stat-card"><div class="stat-value"><%= stats.total_trips %></div><div class="stat-label">Total Trips</div></div>
<div class="stat-card"><div class="stat-value"><%= stats.month_trips %></div><div class="stat-label">This Month</div></div>
<div class="stat-card"><div class="stat-value">₹<%= stats.total_revenue.toLocaleString('en-IN') %></div><div class="stat-label">💰 Revenue</div></div>
<div class="stat-card"><div class="stat-value">₹<%= stats.total_expenses.toLocaleString('en-IN') %></div><div class="stat-label">💸 Expenses</div></div>
</div>
<div class="card card-accent" style="border-left-color:<%= stats.profit >= 0 ? 'var(--green)' : 'red' %>;padding:var(--space-md);text-align:center">
<div style="font-size:0.85rem;color:var(--gray-700)">Net Profit</div>
<div style="font-size:1.5rem;font-weight:700;color:<%= stats.profit >= 0 ? 'green' : 'red' %>">₹<%= stats.profit.toLocaleString('en-IN') %></div>
</div>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,40 @@
<% var title = 'Return Load'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🔄 Return Load</h2>
<% if (availability && availability.status === 'looking') { %>
<div class="card card-accent" style="border-left-color:var(--green);margin-bottom:var(--space-md);padding:var(--space-md)">
<div style="display:flex;justify-content:space-between;align-items:center">
<div><strong>✅ You're visible in <%= availability.current_city %></strong><div style="font-size:0.8rem;color:var(--gray-700)">Shippers can find you</div></div>
<form method="POST" action="/returnload/cancel" style="margin:0"><button class="btn btn-sm btn-outline">Cancel</button></form>
</div>
</div>
<% } else { %>
<form method="POST" action="/returnload/available" class="card" style="padding:var(--space-md);margin-bottom:var(--space-md)">
<h4 style="margin-bottom:var(--space-sm)">📍 Where are you now?</h4>
<div style="display:grid;gap:var(--space-sm)">
<input type="text" name="current_city" class="form-input" placeholder="📍 Current City" required>
<input type="text" name="home_city" class="form-input" placeholder="🏠 Home City (optional)">
<select name="vehicle_type" class="form-input form-select">
<option value="open">Open Body</option><option value="container">Container</option><option value="trailer">Trailer</option>
</select>
<button type="submit" class="btn btn-primary btn-block">🔍 Find Return Loads</button>
</div>
</form>
<% } %>
<% if (suggestions.length > 0) { %>
<h3 style="font-size:1rem;margin-bottom:var(--space-sm)">📋 Available Loads</h3>
<% suggestions.forEach(load => { %>
<a href="/loadboard/<%= load.id %>" class="card card-accent" style="display:block;text-decoration:none;color:inherit;margin-bottom:var(--space-sm)">
<strong>📍 <%= load.origin_city %> → <%= load.destination_city %></strong>
<div style="font-size:0.8rem;color:var(--gray-700)"><%= load.weight_tons %> tons | ₹<%= (load.budget||0).toLocaleString('en-IN') %></div>
</a>
<% }) %>
<% } else if (availability) { %>
<div class="card text-center" style="padding:var(--space-xl)"><p>📭 No loads from your city yet. We'll notify you!</p></div>
<% } %>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,18 @@
<% var title = is_sos ? 'SOS' : 'Check-in'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container text-center">
<div style="font-size:3rem;margin-bottom:var(--space-md)"><%= is_sos ? '🆘' : '✅' %></div>
<h2 style="font-size:1.2rem;margin-bottom:var(--space-md)"><%= is_sos ? 'SOS Alert Ready' : 'Check-in Ready' %></h2>
<p style="color:var(--gray-700);font-size:0.85rem;margin-bottom:var(--space-lg)">Tap to send via WhatsApp:</p>
<div style="display:grid;gap:var(--space-sm)">
<% links.forEach(l => { %>
<a href="<%= l.url %>" target="_blank" class="btn btn-block <%= is_sos ? '' : 'btn-primary' %>" style="<%= is_sos ? 'background:red;color:#fff' : '' %>">💬 <%= l.name %></a>
<% if (l.call) { %><a href="<%= l.call %>" class="btn btn-outline btn-block btn-sm">📞 Call <%= l.name %></a><% } %>
<% }) %>
</div>
<a href="/safety" class="btn btn-outline btn-block" style="margin-top:var(--space-lg)">← Back</a>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,49 @@
<% var title = 'Safety'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🛡️ Safety</h2>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md);margin-bottom:var(--space-lg)">
<form method="POST" action="/safety/checkin" style="margin:0">
<input type="hidden" name="message" value="I am safe.">
<button type="submit" class="icon-action-btn" style="width:100%;border-color:var(--green)">
<span class="icon-action-emoji">✅</span><span class="icon-action-label">I'm Safe</span>
</button>
</form>
<form method="POST" action="/safety/sos" style="margin:0">
<input type="hidden" name="emergency_type" value="Emergency">
<button type="submit" class="icon-action-btn" style="width:100%;border-color:red;color:red">
<span class="icon-action-emoji">🆘</span><span class="icon-action-label">SOS</span>
</button>
</form>
</div>
<div class="card" style="padding:var(--space-md);margin-bottom:var(--space-md);background:#fff3e0">
<strong>📞 Emergency:</strong>
<div style="display:flex;gap:var(--space-sm);margin-top:8px;flex-wrap:wrap">
<a href="tel:100" class="btn btn-sm btn-outline">🚔 100</a>
<a href="tel:108" class="btn btn-sm btn-outline">🚑 108</a>
<a href="tel:1033" class="btn btn-sm btn-outline">🛣️ 1033</a>
</div>
</div>
<h3 style="font-size:1rem;margin-bottom:var(--space-sm)">👨‍👩‍👧 Family Contacts</h3>
<% contacts.forEach(c => { %>
<div class="card" style="padding:12px;margin-bottom:8px;display:flex;justify-content:space-between;align-items:center">
<div><strong><%= c.contact_name %></strong><div style="font-size:0.75rem;color:var(--gray-700)"><%= c.contact_phone %> • <%= c.relationship %></div></div>
<form method="POST" action="/safety/contacts/delete/<%= c.id %>" style="margin:0"><button class="btn btn-sm" style="color:red">✕</button></form>
</div>
<% }) %>
<form method="POST" action="/safety/contacts/add" class="card" style="padding:var(--space-md);margin-top:var(--space-md)">
<h4 style="margin-bottom:var(--space-sm)"> Add Contact</h4>
<div style="display:grid;gap:var(--space-sm)">
<input type="text" name="contact_name" class="form-input" placeholder="👤 Name" required>
<input type="tel" name="contact_phone" class="form-input" placeholder="📱 Phone" required maxlength="10">
<select name="relationship" class="form-input form-select">
<option value="family">👨‍👩‍👧 Family</option><option value="spouse">💑 Spouse</option><option value="friend">🤝 Friend</option>
</select>
<button type="submit" class="btn btn-primary btn-block">Save</button>
</div>
</form>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -0,0 +1,24 @@
<% var title = 'Search'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🔍 Search</h2>
<form method="GET" action="/search" style="margin-bottom:var(--space-md)">
<div style="display:grid;grid-template-columns:1fr auto;gap:var(--space-sm)">
<input type="text" name="q" class="form-input" placeholder="Search loads, users, ads..." value="<%= q %>" autofocus>
<button type="submit" class="btn btn-primary">🔍</button>
</div>
</form>
<% if (q) { %>
<% if (results.loads.length) { %><h3 style="font-size:1rem;margin:var(--space-md) 0 var(--space-sm)">📋 Loads (<%= results.loads.length %>)</h3>
<% results.loads.forEach(l => { %><a href="/loadboard/<%= l.id %>" class="card" style="display:block;padding:10px;margin-bottom:6px;text-decoration:none;color:inherit"><strong><%= l.origin_city %> → <%= l.destination_city %></strong> <% if(l.budget){%>| ₹<%= l.budget.toLocaleString('en-IN') %><%}%></a><% }) } %>
<% if (results.users.length) { %><h3 style="font-size:1rem;margin:var(--space-md) 0 var(--space-sm)">👤 Users (<%= results.users.length %>)</h3>
<% results.users.forEach(u => { %><div class="card" style="padding:10px;margin-bottom:6px"><strong><%= u.name || u.username %></strong> <span class="badge"><%= u.role %></span></div><% }) } %>
<% if (results.classifieds.length) { %><h3 style="font-size:1rem;margin:var(--space-md) 0 var(--space-sm)">🛒 Classifieds (<%= results.classifieds.length %>)</h3>
<% results.classifieds.forEach(c => { %><div class="card" style="padding:10px;margin-bottom:6px"><strong><%= c.title %></strong> | ₹<%= (c.price||0).toLocaleString('en-IN') %></div><% }) } %>
<% if (!results.loads.length && !results.users.length && !results.classifieds.length) { %><div class="card text-center" style="padding:var(--space-xl)"><p>No results for "<%= q %>"</p></div><% } %>
<% } %>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -1,25 +1,25 @@
<% var title = 'शिपर डैशबोर्ड'; %> <% var title = t('dashboard.shipperTitle'); %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)"> <section class="section" style="padding-top:var(--space-lg)">
<div class="container"> <div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">📦 नमस्ते, <%= user.name %>!</h2> <h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">📦 <%= t('dashboard.hello') %>, <%= user.name %>!</h2>
<div class="stats-grid"> <div class="stats-grid">
<div class="stat-card"><div class="stat-value"><%= stats.totalLoads %></div><div class="stat-label">मेरे लोड</div></div> <div class="stat-card"><div class="stat-value"><%= stats.totalLoads %></div><div class="stat-label"><%= t('dashboard.myLoads') %></div></div>
<div class="stat-card"><div class="stat-value"><%= stats.openLoads %></div><div class="stat-label">खुले लोड</div></div> <div class="stat-card"><div class="stat-value"><%= stats.openLoads %></div><div class="stat-label"><%= t('dashboard.openLoads') %></div></div>
<div class="stat-card"><div class="stat-value"><%= stats.activeTrips %></div><div class="stat-label">सक्रिय शिपमेंट</div></div> <div class="stat-card"><div class="stat-value"><%= stats.activeTrips %></div><div class="stat-label"><%= t('dashboard.activeShipments') %></div></div>
</div> </div>
<% if (recentLoads.length > 0) { %> <% if (recentLoads.length > 0) { %>
<h3 style="font-size:1rem;margin-top:var(--space-lg);margin-bottom:var(--space-sm)">📋 हाल के लोड</h3> <h3 style="font-size:1rem;margin-top:var(--space-lg);margin-bottom:var(--space-sm)">📋 <%= t('dashboard.recentLoads') %></h3>
<% recentLoads.forEach(load => { %> <% recentLoads.forEach(load => { %>
<a href="/loadboard/<%= load.id %>" class="card card-accent" style="display:block;text-decoration:none;color:inherit;margin-bottom:var(--space-sm)"> <a href="/loadboard/<%= load.id %>" class="card card-accent" style="display:block;text-decoration:none;color:inherit;margin-bottom:var(--space-sm)">
<div style="display:flex;justify-content:space-between;align-items:center"> <div style="display:flex;justify-content:space-between;align-items:center">
<div> <div>
<strong><%= load.origin_city %> → <%= load.destination_city %></strong> <strong><%= load.origin_city %> → <%= load.destination_city %></strong>
<div style="font-size:0.8rem;color:var(--gray-700)"><%= load.weight_tons %> टन | 🏷️ <%= load.bid_count %> बोली</div> <div style="font-size:0.8rem;color:var(--gray-700)"><%= load.weight_tons %> <%= t('common.tons') %> | 🏷️ <%= load.bid_count %> <%= t('common.bids') %></div>
</div> </div>
<span class="badge badge-<%= load.status === 'open' ? 'open' : 'booked' %>"><%= load.status %></span> <span class="badge badge-<%= load.status === 'open' ? 'open' : 'booked' %>"><%= load.status %></span>
</div> </div>
@ -27,9 +27,39 @@
<% }) %> <% }) %>
<% } %> <% } %>
<div style="margin-top:var(--space-lg);display:grid;gap:var(--space-sm)"> <div style="margin-top:var(--space-lg);display:grid;grid-template-columns:1fr 1fr;gap:var(--space-md)">
<a href="/loadboard/post" class="btn btn-cta btn-block">+ नया लोड पोस्ट करें</a> <a href="/loadboard/post" class="icon-action-btn">
<a href="/trips" class="btn btn-outline btn-block">🚚 मेरी शिपमेंट</a> <span class="icon-action-emoji"></span>
<span class="icon-action-label"><%= t('actions.postLoad') %></span>
</a>
<a href="/trips" class="icon-action-btn">
<span class="icon-action-emoji">🚚</span>
<span class="icon-action-label"><%= t('dashboard.activeShipments') %></span>
</a>
<a href="/invoice" class="icon-action-btn">
<span class="icon-action-emoji">🧾</span>
<span class="icon-action-label">Invoice</span>
</a>
<a href="/rates" class="icon-action-btn">
<span class="icon-action-emoji">📊</span>
<span class="icon-action-label">Rates</span>
</a>
<a href="/fleet" class="icon-action-btn">
<span class="icon-action-emoji">🚛</span>
<span class="icon-action-label">Fleet</span>
</a>
<a href="/classifieds" class="icon-action-btn">
<span class="icon-action-emoji">🛒</span>
<span class="icon-action-label">Buy/Sell</span>
</a>
<a href="/messages" class="icon-action-btn">
<span class="icon-action-emoji">💬</span>
<span class="icon-action-label"><%= t('nav.messages') %></span>
</a>
<a href="/notifications" class="icon-action-btn">
<span class="icon-action-emoji">🔔</span>
<span class="icon-action-label">Alerts</span>
</a>
</div> </div>
</div> </div>
</section> </section>

View file

@ -0,0 +1,43 @@
<% var title = 'Trip Planner'; %>
<%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)">
<div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🧮 Trip Cost Calculator</h2>
<form method="POST" action="/trip-planner/calculate" class="card" style="padding:var(--space-md)">
<div style="display:grid;gap:var(--space-sm)">
<div><label class="form-label">📍 From</label><input type="text" name="origin" class="form-input" list="cities" placeholder="Mumbai" required></div>
<div><label class="form-label">📍 To</label><input type="text" name="destination" class="form-input" list="cities" placeholder="Delhi" required></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:var(--space-sm)">
<div><label class="form-label">⛽ Mileage (km/l)</label><input type="number" name="mileage" class="form-input" value="4" step="0.5"></div>
<div><label class="form-label">💰 Freight ₹</label><input type="number" name="freight_charged" class="form-input" placeholder="Optional"></div>
</div>
<button type="submit" class="btn btn-primary btn-block">🧮 Calculate</button>
</div>
</form>
<datalist id="cities"><% cities.forEach(c => { %><option value="<%= c %>"><% }) %></datalist>
<% if (result && !result.error) { %>
<div class="card" style="margin-top:var(--space-md);padding:var(--space-md)">
<h3 style="font-size:1rem;margin-bottom:var(--space-sm)">📊 <%= result.origin %> → <%= result.destination %></h3>
<div style="font-size:0.85rem;color:var(--gray-700);margin-bottom:var(--space-md)">📏 <%= result.distance_km %> km | ⏱️ ~<%= result.hours %> hrs | 🚧 <%= result.toll_plazas %> tolls</div>
<div class="stats-grid">
<div class="stat-card"><div class="stat-value">₹<%= result.fuel.cost.toLocaleString('en-IN') %></div><div class="stat-label">⛽ Fuel</div></div>
<div class="stat-card"><div class="stat-value">₹<%= result.toll.toLocaleString('en-IN') %></div><div class="stat-label">🚧 Toll</div></div>
<div class="stat-card"><div class="stat-value">₹<%= result.driver_bata.toLocaleString('en-IN') %></div><div class="stat-label">🍽️ Bata</div></div>
<div class="stat-card"><div class="stat-value">₹<%= result.total.toLocaleString('en-IN') %></div><div class="stat-label">💸 Total</div></div>
</div>
<% if (result.profit !== undefined && result.profit !== 0) { %>
<div class="card card-accent" style="margin-top:var(--space-md);border-left-color:<%= result.viable ? 'var(--green)' : 'red' %>">
<div style="display:flex;justify-content:space-between;align-items:center">
<span style="font-weight:700"><%= result.viable ? '✅ Profitable' : '❌ Loss' %></span>
<span style="font-size:1.2rem;font-weight:700;color:<%= result.viable ? 'green' : 'red' %>">₹<%= result.profit.toLocaleString('en-IN') %> (<%= result.margin %>%)</span>
</div>
</div>
<% } %>
</div>
<% } else if (result && result.error) { %>
<div class="card" style="margin-top:var(--space-md);padding:var(--space-md);text-align:center;color:var(--gray-700)">⚠️ <%= result.error %></div>
<% } %>
</div>
</section>
<%- include('../partials/footer') %>

View file

@ -1,14 +1,14 @@
<% var title = 'मेरी ट्रिप'; %> <% var title = t('actions.myTrips'); %>
<%- include('../partials/header') %> <%- include('../partials/header') %>
<div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div> <div class="tricolor-strip"><div class="tricolor-saffron"></div><div class="tricolor-white"></div><div class="tricolor-green"></div></div>
<section class="section" style="padding-top:var(--space-lg)"> <section class="section" style="padding-top:var(--space-lg)">
<div class="container"> <div class="container">
<h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🚚 मेरी ट्रिप</h2> <h2 style="font-size:1.3rem;margin-bottom:var(--space-md)">🚚 <%= t('actions.myTrips') %></h2>
<% if (trips.length === 0) { %> <% if (trips.length === 0) { %>
<div class="card text-center" style="padding:var(--space-2xl)"> <div class="card text-center" style="padding:var(--space-2xl)">
<p>कोई ट्रिप नहीं</p> <p><%= t('trips.noTrips') %></p>
</div> </div>
<% } else { %> <% } else { %>
<div style="display:grid;gap:var(--space-md)"> <div style="display:grid;gap:var(--space-md)">
@ -19,7 +19,7 @@
<strong>📍 <%= trip.load ? trip.load.origin_city + ' → ' + trip.load.destination_city : 'N/A' %></strong> <strong>📍 <%= trip.load ? trip.load.origin_city + ' → ' + trip.load.destination_city : 'N/A' %></strong>
<div style="font-size:0.8rem;color:var(--gray-700);margin-top:4px"> <div style="font-size:0.8rem;color:var(--gray-700);margin-top:4px">
₹<%= Number(trip.amount).toLocaleString('en-IN') %> ₹<%= Number(trip.amount).toLocaleString('en-IN') %>
<% if (trip.load) { %> | <%= trip.load.weight_tons %> टन | <%= trip.load.truck_type %><% } %> <% if (trip.load) { %> | <%= trip.load.weight_tons %> <%= t('common.tons') %> | <%= trip.load.truck_type %><% } %>
</div> </div>
</div> </div>
<span class="badge badge-<%= trip.status === 'delivered' ? 'delivered' : trip.status === 'cancelled' ? 'cancelled' : 'transit' %>"><%= trip.status %></span> <span class="badge badge-<%= trip.status === 'delivered' ? 'delivered' : trip.status === 'cancelled' ? 'cancelled' : 'transit' %>"><%= trip.status %></span>
@ -28,11 +28,11 @@
<% if (user.role === 'driver' && trip.status !== 'delivered' && trip.status !== 'cancelled') { %> <% if (user.role === 'driver' && trip.status !== 'delivered' && trip.status !== 'cancelled') { %>
<div style="margin-top:var(--space-md);display:flex;gap:var(--space-sm)"> <div style="margin-top:var(--space-md);display:flex;gap:var(--space-sm)">
<% if (trip.status === 'confirmed') { %> <% if (trip.status === 'confirmed') { %>
<form method="POST" action="/trips/<%= trip.id %>/status"><input type="hidden" name="status" value="picked_up"><button class="btn btn-primary btn-sm">पिकअप किया</button></form> <form method="POST" action="/trips/<%= trip.id %>/status"><input type="hidden" name="status" value="picked_up"><button class="btn btn-primary btn-sm">📦 <%= t('trips.pickedUp') %></button></form>
<% } else if (trip.status === 'picked_up') { %> <% } else if (trip.status === 'picked_up') { %>
<form method="POST" action="/trips/<%= trip.id %>/status"><input type="hidden" name="status" value="in_transit"><button class="btn btn-primary btn-sm">रास्ते में</button></form> <form method="POST" action="/trips/<%= trip.id %>/status"><input type="hidden" name="status" value="in_transit"><button class="btn btn-primary btn-sm">🚛 <%= t('trips.inTransit') %></button></form>
<% } else if (trip.status === 'in_transit') { %> <% } else if (trip.status === 'in_transit') { %>
<form method="POST" action="/trips/<%= trip.id %>/status"><input type="hidden" name="status" value="delivered"><button class="btn btn-success btn-sm">पहुँचा दिया ✓</button></form> <form method="POST" action="/trips/<%= trip.id %>/status"><input type="hidden" name="status" value="delivered"><button class="btn btn-success btn-sm">✅ <%= t('trips.delivered') %></button></form>
<% } %> <% } %>
</div> </div>
<% } %> <% } %>

View file

@ -1,13 +1,31 @@
<% if (user) { %> <% if (user) { %>
<nav class="bottom-nav"> <nav class="bottom-nav" aria-label="Main navigation">
<a href="/<%= user.role %>" class="bnav-item"><span class="bnav-icon">🏠</span><span>होम</span></a> <a href="/<%= user.role %>" class="bnav-item">
<a href="/loadboard" class="bnav-item"><span class="bnav-icon">📋</span><span>लोड</span></a> <span class="bnav-icon-lg">🏠</span>
<span class="bnav-label"><%= t('nav.home') %></span>
</a>
<a href="/loadboard" class="bnav-item">
<span class="bnav-icon-lg">📋</span>
<span class="bnav-label"><%= t('nav.loads') %></span>
</a>
<% if (user.role === 'shipper' || user.role === 'broker') { %> <% if (user.role === 'shipper' || user.role === 'broker') { %>
<a href="/loadboard/post" class="bnav-item bnav-add"><span class="bnav-icon"></span><span>पोस्ट</span></a> <a href="/loadboard/post" class="bnav-item bnav-add">
<span class="bnav-icon-lg"></span>
<span class="bnav-label"><%= t('nav.post') %></span>
</a>
<% } else { %> <% } else { %>
<a href="/trips" class="bnav-item"><span class="bnav-icon">🚚</span><span>ट्रिप</span></a> <a href="/search" class="bnav-item">
<span class="bnav-icon-lg">🔍</span>
<span class="bnav-label">Search</span>
</a>
<% } %> <% } %>
<a href="/messages" class="bnav-item"><span class="bnav-icon">💬</span><span>संदेश</span></a> <a href="/notifications" class="bnav-item">
<a href="/profile" class="bnav-item"><span class="bnav-icon">👤</span><span>प्रोफ़ाइल</span></a> <span class="bnav-icon-lg">🔔</span>
<span class="bnav-label">Alerts</span>
</a>
<a href="/more" class="bnav-item">
<span class="bnav-icon-lg">⋯</span>
<span class="bnav-label">More</span>
</a>
</nav> </nav>
<% } %> <% } %>

View file

@ -2,16 +2,13 @@
<footer class="govt-footer"> <footer class="govt-footer">
<div class="footer-inner"> <div class="footer-inner">
<div class="footer-brand"> <div class="footer-brand">
<strong>भारत ट्रक्स | BharathTrucks</strong> <strong><%= t('common.appName') %> | BharathTrucks</strong>
<p>राष्ट्रीय माल परिवहन मंच</p> <p><%= t('common.subtitle') %></p>
</div> </div>
<div class="footer-links"> <p class="footer-copy">© 2026 BharathTrucks.</p>
<a href="/about">हमारे बारे में</a>
<a href="/contact">संपर्क करें</a>
</div>
<p class="footer-copy">© 2026 BharathTrucks. सर्वाधिकार सुरक्षित।</p>
</div> </div>
</footer> </footer>
<script src="/js/app.js"></script> <script src="/js/app.js"></script>
<script src="/js/voice.js"></script>
</body> </body>
</html> </html>

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
<meta name="theme-color" content="#1a237e"> <meta name="theme-color" content="#1a237e">
<title><%= typeof title !== 'undefined' ? title + ' | भारत ट्रक्स' : 'भारत ट्रक्स' %></title> <title><%= typeof title !== 'undefined' ? title + ' | ' + t('common.appName') : t('common.appName') %></title>
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;600;700&family=Noto+Sans+Devanagari:wght@400;600;700&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Noto+Sans:wght@400;600;700&family=Noto+Sans+Devanagari:wght@400;600;700&display=swap" rel="stylesheet">
@ -18,17 +18,23 @@
<div class="header-brand"> <div class="header-brand">
<div class="header-emblem">🏛️</div> <div class="header-emblem">🏛️</div>
<div class="header-titles"> <div class="header-titles">
<h1 class="header-title-hi">भारत ट्रक्स</h1> <h1 class="header-title-hi"><%= t('common.appName') %></h1>
<p class="header-subtitle">राष्ट्रीय माल परिवहन मंच | National Freight Transport Platform</p> <p class="header-subtitle"><%= t('common.subtitle') %></p>
</div> </div>
</div> </div>
<nav class="header-nav"> <nav class="header-nav">
<div class="lang-switcher">
<a href="/lang/hi" class="lang-btn <%= lang === 'hi' ? 'active' : '' %>" title="हिंदी">हि</a>
<a href="/lang/en" class="lang-btn <%= lang === 'en' ? 'active' : '' %>" title="English">EN</a>
<a href="/lang/ta" class="lang-btn <%= lang === 'ta' ? 'active' : '' %>" title="தமிழ்">த</a>
<a href="/lang/te" class="lang-btn <%= lang === 'te' ? 'active' : '' %>" title="తెలుగు">తె</a>
</div>
<% if (user) { %> <% if (user) { %>
<span class="header-user"><%= user.name || user.username %></span> <span class="header-user"><%= user.name || user.username %></span>
<a href="/logout" class="header-link">लॉगआउट</a> <a href="/logout" class="header-link"><%= t('actions.logout') %></a>
<% } else { %> <% } else { %>
<a href="/login" class="header-link">लॉगिन</a> <a href="/login" class="header-link"><%= t('actions.login') %></a>
<a href="/register" class="btn-header-cta">पंजीकरण</a> <a href="/register" class="btn-header-cta"><%= t('actions.register') %></a>
<% } %> <% } %>
</nav> </nav>
</div> </div>

View file

@ -0,0 +1,106 @@
-- BharathTrucks Phase 1 Migration: Driver tools, Safety, Maintenance, FASTag, Notifications
-- Run this AFTER supabase-FULL-migration.sql
-- Driver personal ledger (replaces paper diary)
CREATE TABLE IF NOT EXISTS driver_ledger (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES app_users(id) ON DELETE CASCADE,
origin TEXT,
destination TEXT,
trip_date DATE DEFAULT CURRENT_DATE,
freight_received NUMERIC(10,2) DEFAULT 0,
fuel_cost NUMERIC(10,2) DEFAULT 0,
toll_cost NUMERIC(10,2) DEFAULT 0,
other_expense NUMERIC(10,2) DEFAULT 0,
notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_driver_ledger_user ON driver_ledger(user_id);
-- Return load availability
CREATE TABLE IF NOT EXISTS available_for_return (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES app_users(id) ON DELETE CASCADE UNIQUE,
current_city TEXT NOT NULL,
home_city TEXT,
vehicle_type TEXT,
status TEXT DEFAULT 'looking',
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_return_city ON available_for_return(current_city, status);
-- Safety contacts
CREATE TABLE IF NOT EXISTS safety_contacts (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES app_users(id) ON DELETE CASCADE,
contact_name TEXT NOT NULL,
contact_phone TEXT NOT NULL,
relationship TEXT DEFAULT 'family',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_safety_contacts_user ON safety_contacts(user_id);
-- Safety check-ins log
CREATE TABLE IF NOT EXISTS safety_checkins (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES app_users(id) ON DELETE CASCADE,
location TEXT,
message TEXT,
is_sos BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Vehicle maintenance reminders
CREATE TABLE IF NOT EXISTS vehicle_reminders (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES app_users(id) ON DELETE CASCADE,
vehicle_number TEXT,
doc_type TEXT NOT NULL, -- insurance, fitness, permit, puc, service
expiry_date DATE NOT NULL,
notes TEXT,
status TEXT DEFAULT 'active',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_reminders_user ON vehicle_reminders(user_id);
CREATE INDEX idx_reminders_expiry ON vehicle_reminders(expiry_date, status);
-- FASTag accounts
CREATE TABLE IF NOT EXISTS fastag_accounts (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES app_users(id) ON DELETE CASCADE UNIQUE,
fastag_number TEXT,
vehicle_number TEXT,
issuer_bank TEXT,
balance NUMERIC(10,2) DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- Toll history
CREATE TABLE IF NOT EXISTS toll_history (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES app_users(id) ON DELETE CASCADE,
type TEXT DEFAULT 'toll', -- toll, recharge
plaza_name TEXT,
amount NUMERIC(10,2) DEFAULT 0,
status TEXT DEFAULT 'completed',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_toll_user ON toll_history(user_id, created_at DESC);
-- Enable RLS
ALTER TABLE driver_ledger ENABLE ROW LEVEL SECURITY;
ALTER TABLE available_for_return ENABLE ROW LEVEL SECURITY;
ALTER TABLE safety_contacts ENABLE ROW LEVEL SECURITY;
ALTER TABLE safety_checkins ENABLE ROW LEVEL SECURITY;
ALTER TABLE vehicle_reminders ENABLE ROW LEVEL SECURITY;
ALTER TABLE fastag_accounts ENABLE ROW LEVEL SECURITY;
ALTER TABLE toll_history ENABLE ROW LEVEL SECURITY;
-- RLS policies (allow all for service role, restrict for anon)
CREATE POLICY "Users manage own ledger" ON driver_ledger FOR ALL USING (true);
CREATE POLICY "Users manage own return" ON available_for_return FOR ALL USING (true);
CREATE POLICY "Users manage own contacts" ON safety_contacts FOR ALL USING (true);
CREATE POLICY "Users manage own checkins" ON safety_checkins FOR ALL USING (true);
CREATE POLICY "Users manage own reminders" ON vehicle_reminders FOR ALL USING (true);
CREATE POLICY "Users manage own fastag" ON fastag_accounts FOR ALL USING (true);
CREATE POLICY "Users manage own tolls" ON toll_history FOR ALL USING (true);

View file

@ -0,0 +1,91 @@
-- BharathTrucks Phase 2 Migration: Gamification, Referral, Feed, Challenges, Invoice
-- Run AFTER supabase-phase1-migration.sql
CREATE TABLE IF NOT EXISTS user_gamification (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES app_users(id) ON DELETE CASCADE UNIQUE,
xp INTEGER DEFAULT 0,
login_streak INTEGER DEFAULT 0,
last_login_date DATE,
steps_completed JSONB DEFAULT '[]',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS user_achievements (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES app_users(id) ON DELETE CASCADE,
achievement_id TEXT NOT NULL,
earned_at TIMESTAMPTZ DEFAULT NOW(),
UNIQUE(user_id, achievement_id)
);
CREATE TABLE IF NOT EXISTS xp_log (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES app_users(id) ON DELETE CASCADE,
action TEXT NOT NULL,
xp_earned INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_xp_log_user ON xp_log(user_id, created_at DESC);
CREATE TABLE IF NOT EXISTS challenge_progress (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES app_users(id) ON DELETE CASCADE,
challenge_id TEXT NOT NULL,
completed_date DATE DEFAULT CURRENT_DATE,
UNIQUE(user_id, challenge_id, completed_date)
);
CREATE TABLE IF NOT EXISTS referrals (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
referrer_id UUID REFERENCES app_users(id) ON DELETE CASCADE,
referred_user_id UUID,
referral_code TEXT,
status TEXT DEFAULT 'pending',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_referrals_referrer ON referrals(referrer_id);
CREATE TABLE IF NOT EXISTS feed_events (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
event_type TEXT NOT NULL,
data JSONB DEFAULT '{}',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_feed_created ON feed_events(created_at DESC);
CREATE TABLE IF NOT EXISTS invoices (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
user_id UUID REFERENCES app_users(id) ON DELETE CASCADE,
invoice_number TEXT UNIQUE,
client_name TEXT,
origin TEXT,
destination TEXT,
amount NUMERIC(10,2) DEFAULT 0,
gst_rate NUMERIC(4,2) DEFAULT 5,
gst_amount NUMERIC(10,2) DEFAULT 0,
total_amount NUMERIC(10,2) DEFAULT 0,
upi_id TEXT,
upi_link TEXT,
notes TEXT,
status TEXT DEFAULT 'unpaid',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX idx_invoices_user ON invoices(user_id, created_at DESC);
-- RLS
ALTER TABLE user_gamification ENABLE ROW LEVEL SECURITY;
ALTER TABLE user_achievements ENABLE ROW LEVEL SECURITY;
ALTER TABLE xp_log ENABLE ROW LEVEL SECURITY;
ALTER TABLE challenge_progress ENABLE ROW LEVEL SECURITY;
ALTER TABLE referrals ENABLE ROW LEVEL SECURITY;
ALTER TABLE feed_events ENABLE ROW LEVEL SECURITY;
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
CREATE POLICY "all_access" ON user_gamification FOR ALL USING (true);
CREATE POLICY "all_access" ON user_achievements FOR ALL USING (true);
CREATE POLICY "all_access" ON xp_log FOR ALL USING (true);
CREATE POLICY "all_access" ON challenge_progress FOR ALL USING (true);
CREATE POLICY "all_access" ON referrals FOR ALL USING (true);
CREATE POLICY "all_access" ON feed_events FOR ALL USING (true);
CREATE POLICY "all_access" ON invoices FOR ALL USING (true);

View file

@ -0,0 +1 @@