Compare commits

..

28 Commits

Author SHA1 Message Date
Shanmuga Krishnan S M
1cbee2f4c8 remove(driver-app): remove rit-driver-app from git repository and add to .gitignore
Some checks are pending
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Waiting to run
2026-08-04 14:35:46 +05:30
Shanmuga Krishnan S M
d81b2682b4 fix(driver-app): remove silent stopSelf on location permission warning so background tracking thread always executes
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 14:31:05 +05:30
Shanmuga Krishnan S M
e5725615be fix(backend): replace Lombok in DriverLocationUpdate with explicit getters/setters to fix HTTP 400 JSON deserialization error
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 14:22:27 +05:30
Shanmuga Krishnan S M
3319868eca fix(driver-app): specify FOREGROUND_SERVICE_TYPE_LOCATION in startForeground call for Android 14/15 compatibility
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 14:18:11 +05:30
Shanmuga Krishnan S M
14b87b9d12 fix(driver-app): change RECEIVER_NOT_EXPORTED to RECEIVER_EXPORTED so MainActivity receives location updates from TrackingService
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 14:16:31 +05:30
Shanmuga Krishnan S M
4ef114fdcd fix(driver-app): guarantee Android 13+ broadcast delivery with explicit package targeting and indoor location fallback
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 14:13:58 +05:30
Shanmuga Krishnan S M
20a0dd842f fix(driver-app): clean up Android Studio XML layout render and button size warnings in activity_main.xml
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 14:09:06 +05:30
Shanmuga Krishnan S M
3397bb5ed1 fix(driver-app): optimize indoor building and stationary location tracking via freshest provider
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 14:02:05 +05:30
Shanmuga Krishnan S M
08974b9cd8 style(hero): set explicit inline color #FFFFFF for Welcome to RIT Freshers Hub text
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 13:57:06 +05:30
Shanmuga Krishnan S M
c1fcc83840 fix(ui): improve hero text contrast to crisp white with drop-shadows, remove false badges and non-working footer links
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 13:54:38 +05:30
Shanmuga Krishnan S M
e8886203ed feat(ui): add Under Development & Mock Data warning banners to Faculty Directory and Notes sections
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 13:40:59 +05:30
Shanmuga Krishnan S M
04c4ed907c feat(chatbot): add AI accuracy warning and disclaimer to initial greeting across Web, Telegram, and Discord
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 13:18:11 +05:30
Shanmuga Krishnan S M
d76e956b07 changes
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 13:07:56 +05:30
Shanmuga Krishnan S M
f198b43e4c changes
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 13:02:22 +05:30
Shanmuga Krishnan S M
c101cf05ed feat(map): add Boys Mess and Staff Mess markers pointing to Homi Jehangir Bhabha Block
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 11:57:32 +05:30
Shanmuga Krishnan S M
fca13b2e2c feat(map): update 3D campus map image and map all 22 pin locations accurately
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 11:51:26 +05:30
Shanmuga Krishnan S M
0ac0646614 ci(gitea): add Gitea Actions deployment workflow and Gitea CI/CD guide
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 11:18:23 +05:30
Shanmuga Krishnan S M
c7891cd992 ci: add GitHub Actions automated deployment workflow and CI/CD guide
Some checks failed
Deploy RIT Freshers Hub to VPS / Deploy to Live VPS (push) Has been cancelled
2026-08-04 11:16:12 +05:30
Shanmuga Krishnan S M
8433fe33b2 docs: add complete VPS operations and maintenance master guide 2026-08-04 09:15:36 +05:30
Shanmuga Krishnan S M
2a86af0113 fix(ui): ensure instant video playback with auto-muted state and high-contrast white text for hero section 2026-08-04 09:08:52 +05:30
Shanmuga Krishnan S M
1852239452 feat(video): add WhatsApp Video asset to public/video for LazyVideoHero background overlay 2026-08-04 09:06:04 +05:30
Shanmuga Krishnan S M
b461c6cf32 fix(ui): activate dark video overlay only when video is playing 2026-08-04 09:03:39 +05:30
Shanmuga Krishnan S M
68902caaa4 feat(ui): add ultra-optimized LazyVideoHero component with IntersectionObserver auto-pause, section-scoping, and CSS layout containment 2026-08-04 08:58:29 +05:30
Shanmuga Krishnan S M
6229128dd2 fix(bot): restrict collab application notifications strictly to author's telegram_chat_id & disable Discord collab functionality 2026-08-04 08:26:41 +05:30
Shanmuga Krishnan S M
7067f7cbad perf and ui: remove heavy video, strip RAG and Gemini labels, and deliver sleek modern UI 2026-08-04 08:06:25 +05:30
Shanmuga Krishnan S M
674eb437b4 fix(bus-routes): normalize route number matching for R01 vs 1 vs R15 formats 2026-08-03 14:44:49 +05:30
Shanmuga Krishnan S M
5676bb0950 feat(driver-app): add continuous 5s periodic uploader timer fallback in TrackingService 2026-08-03 14:41:49 +05:30
Shanmuga Krishnan S M
add0fbb47c fix(driver-app): use Locale.US in NetworkHelper to prevent invalid JSON formatting 2026-08-03 14:36:20 +05:30
40 changed files with 792 additions and 1520 deletions

View File

@@ -0,0 +1,42 @@
name: Deploy RIT Freshers Hub to VPS
on:
push:
branches:
- main
jobs:
deploy:
name: Deploy to Live VPS
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: SSH into VPS and Deploy
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USERNAME }}
key: ${{ secrets.VPS_SSH_KEY }}
port: 22
script: |
echo "🚀 Starting Automated CI/CD Deployment from Gitea..."
cd /var/www/freshers-hub
echo "📥 Pulling latest code from Gitea..."
git pull origin main
echo "⚡ Building Frontend PWA..."
npm install --production=false
npm run build
echo "🤖 Building Go Chatbot..."
cd /var/www/freshers-hub/chatbot-service
go build -o chatbot-service main.go
echo "🔄 Restarting Systemd Services..."
systemctl restart springboot chatbot telegram-bot nginx
echo "🎉 Deployment Completed Successfully!"

42
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
name: Deploy RIT Freshers Hub to VPS
on:
push:
branches:
- main
jobs:
deploy:
name: Deploy to Live VPS
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: SSH into VPS and Deploy
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.VPS_HOST }}
username: ${{ secrets.VPS_USERNAME }}
key: ${{ secrets.VPS_SSH_KEY }}
port: 22
script: |
echo "🚀 Starting Automated CI/CD Deployment..."
cd /var/www/freshers-hub
echo "📥 Pulling latest code from GitHub..."
git pull origin main
echo "⚡ Building Frontend PWA..."
npm install --production=false
npm run build
echo "🤖 Building Go Chatbot..."
cd /var/www/freshers-hub/chatbot-service
go build -o chatbot-service main.go
echo "🔄 Restarting Systemd Services..."
systemctl restart springboot chatbot telegram-bot nginx
echo "🎉 Deployment Completed Successfully!"

View File

@@ -143,9 +143,7 @@ cd freshers-hub
--- ---
## Step 6: Build & Run Component 2 (Spring Boot Java Backend - Port 8085)
1. **Build the Spring Boot `.jar` file:**
```bash ```bash
cd /var/www/freshers-hub/backend cd /var/www/freshers-hub/backend
chmod +x mvnw chmod +x mvnw

84
CICD_GUIDE.md Normal file
View File

@@ -0,0 +1,84 @@
# 🔄 RIT Freshers Hub - Step-by-Step CI/CD Pipeline Guide
Automate your live server deployments with **GitHub Actions** so that whenever you run `git push origin main`, your website, backend, chatbot, and services update on your VPS automatically in 30 seconds!
---
## 🏗️ How the CI/CD Pipeline Works
```mermaid
graph LR
A[💻 Developer Pushes Code] -->|git push origin main| B[🐙 GitHub Repository]
B -->|Triggers Workflow| C[⚡ GitHub Actions Runner]
C -->|Secure SSH Connection| D[🌐 VPS Server 129.121.126.66]
D -->|1. git pull| E[📥 Update Code]
E -->|2. npm run build| F[⚡ Build PWA Frontend]
F -->|3. go build| G[🤖 Build Go Engine]
G -->|4. systemctl restart| H[🚀 Live Site Updated!]
```
---
## 🛠️ Step-by-Step Setup Guide
### Step 1: Add SSH Secrets to your GitHub Repository
1. Open your GitHub Repository in your web browser:
`https://github.com/Amudieshwar-AG/Freshers-Hub`
2. Click **Settings** (top toolbar) ⚙️.
3. In the left sidebar, expand **Secrets and variables** ➔ click **Actions**.
4. Click **New repository secret** (green button on top right) and add these **3 secrets**:
---
#### 🔑 Secret 1: `VPS_HOST`
* **Name:** `VPS_HOST`
* **Secret Value:** `129.121.126.66`
#### 🔑 Secret 2: `VPS_USERNAME`
* **Name:** `VPS_USERNAME`
* **Secret Value:** `root`
#### 🔑 Secret 3: `VPS_SSH_KEY`
* **Name:** `VPS_SSH_KEY`
* **Secret Value:** Paste your SSH Private Key contents.
* *Where to find your key on VPS:* Run `cat ~/.ssh/id_rsa` or `cat ~/.ssh/id_ed25519` on your server terminal.
* Copy the entire key including `-----BEGIN OPENSSH PRIVATE KEY-----` and `-----END OPENSSH PRIVATE KEY-----`.
---
### Step 2: Push the Workflow File to GitHub
The deployment workflow file is already created at:
[.github/workflows/deploy.yml](file:///f:/fresher/Freshers-Hub/.github/workflows/deploy.yml)
Push it to GitHub by running in your local terminal:
```bash
git add .github/workflows/deploy.yml CICD_GUIDE.md
git commit -m "ci: add GitHub Actions automated deployment workflow"
git push origin main
```
---
### Step 3: Test your CI/CD Pipeline! 🎉
1. Make any code edit or change on your project.
2. Push your changes:
```bash
git push origin main
```
3. Go to your **GitHub Repository** ➔ Click the **Actions** tab.
4. You will see a live workflow named **"Deploy RIT Freshers Hub to VPS"** running!
5. In ~30 seconds, it will complete with a green checkmark ``, and your live server at `https://rit-services.in` will be updated automatically!
---
## 🛑 Troubleshooting CI/CD Pipeline Issues
* **Error: `ssh: handshake failed` or `permission denied (publickey)`**
* **Fix:** Make sure the public key corresponding to `VPS_SSH_KEY` is added to `/root/.ssh/authorized_keys` on your VPS.
* On your VPS, run: `cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys`
* **Error: `Host key verification failed`**
* **Fix:** `appleboy/ssh-action` handles host key verification automatically, but ensure `VPS_HOST` is set strictly to `129.121.126.66` without extra spaces or `http://`.

62
GITEA_CICD_GUIDE.md Normal file
View File

@@ -0,0 +1,62 @@
# 🦊 RIT Freshers Hub - Gitea CI/CD Pipeline Guide
Automate your deployments from your self-hosted Gitea repository (`https://git.rit-services.in/ShanmugaKrishnanSM/RIT-nexus.git`) directly to your live VPS!
---
## 🛠️ Choose Your Preferred Gitea CI/CD Method:
* **Method 1: Gitea Actions (Recommended if Gitea Actions is enabled)** — Uses `.gitea/workflows/deploy.yml` with identical syntax to GitHub Actions.
* **Method 2: Gitea Webhook + Lightweight VPS Script (Fastest & Zero Setup)** — Gitea sends an HTTP trigger on push to automatically execute `git pull && npm run build` on your VPS.
---
## 🚀 Method 1: Setting Up Gitea Actions
### Step 1: Add Secrets in Gitea
1. Open your repository in Gitea:
`https://git.rit-services.in/ShanmugaKrishnanSM/RIT-nexus`
2. Click **Settings** (top right) ➔ **Actions****Secrets**.
3. Add these 3 secrets:
* **`VPS_HOST`**: `129.121.126.66`
* **`VPS_USERNAME`**: `root`
* **`VPS_SSH_KEY`**: Paste your SSH Private Key (`cat ~/.ssh/id_rsa` from VPS).
### Step 2: Push Workflow File
The Gitea workflow file is located at [.gitea/workflows/deploy.yml](file:///f:/fresher/Freshers-Hub/.gitea/workflows/deploy.yml).
Every `git push origin main` will automatically run the deployment steps on your VPS!
---
## ⚡ Method 2: Setting Up Gitea Webhook (Lightweight & Instant)
If Gitea Actions runner is not active on your Gitea instance, you can use a native **Gitea Webhook**:
### Step 1: Create a Deploy Script on VPS
On your VPS terminal (`ssh root@129.121.126.66`), create a deployment shell script:
```bash
cat << 'EOF' > /var/www/freshers-hub/deploy.sh
#!/bin/bash
echo "🚀 Deploying latest code from Gitea..."
cd /var/www/freshers-hub
git pull origin main
npm run build
cd chatbot-service && go build -o chatbot-service main.go
systemctl restart springboot chatbot telegram-bot nginx
echo "✅ Deployment finished!"
EOF
chmod +x /var/www/freshers-hub/deploy.sh
```
### Step 2: Add Webhook in Gitea
1. Go to your Gitea Repo ➔ **Settings****Webhooks**.
2. Click **Add Webhook** ➔ Select **Gitea**.
3. **Target URL:** `http://129.121.126.66:9000/hooks/deploy` (or your webhook port).
4. **HTTP Method:** `POST`
5. **Trigger On:** Push Events.
6. Click **Add Webhook**.
Whenever you push code to Gitea, it triggers `deploy.sh` on your VPS instantly!

223
VPS_MASTER_GUIDE.md Normal file
View File

@@ -0,0 +1,223 @@
# 🚀 RIT Freshers Hub - Complete VPS Operations & Maintenance Guide
A complete, beginner-friendly guide to managing, updating, monitoring, and troubleshooting the **RIT Freshers Hub** production server on Linux (Ubuntu / Debian).
---
## 📍 Server & Architecture Summary
* **Server IP:** `129.121.126.66`
* **SSH User:** `root`
* **Primary Domain:** `rit-services.in`
* **Backend API Domain:** `api.rit-services.in`
* **Application Root Path:** `/var/www/freshers-hub`
### 🏗️ Microservice Ports & Services
| Service | Technology | Port | Systemd Service Name | Config Path |
| :--- | :--- | :--- | :--- | :--- |
| **Frontend PWA** | React + Vite + Nginx | `80` / `443` | `nginx` | `/etc/nginx/sites-available/default` |
| **Backend API** | Spring Boot (Java 17) | `8085` | `springboot.service` | `/etc/systemd/system/springboot.service` |
| **Go Chatbot** | Go BM25 Engine | `8081` | `chatbot.service` | `/etc/systemd/system/chatbot.service` |
| **Bots (Telegram/Discord)** | Python FastAPI | `8082` | `telegram-bot.service` | `/var/www/freshers-hub/telegram-bot/.env` |
| **Database** | PostgreSQL | `5432` | `postgresql` | DB User: `postgres`, Password: `RITHosting123` |
---
## 🔑 1. How to Connect to the VPS
Open PowerShell, Command Prompt, or Terminal on your computer and run:
```bash
ssh root@129.121.126.66
```
*(Enter your SSH password when prompted)*
---
## ⚡ 2. Daily Commands (Deploying Updates from GitHub)
Whenever you push new code to GitHub and want to update the live website:
### A) Update Everything in 1 Command Block
Copy and paste this into your VPS terminal:
```bash
cd /var/www/freshers-hub && \
git pull && \
npm run build && \
(cd chatbot-service && go build -o chatbot-service main.go) && \
systemctl restart springboot chatbot telegram-bot nginx
```
---
### B) Updating Specific Parts (Step-by-Step)
#### 1. Update Frontend UI & Static Files Only:
```bash
cd /var/www/freshers-hub
git pull
npm run build
```
#### 2. Restart Spring Boot Backend:
```bash
systemctl restart springboot
```
#### 3. Restart Go Chatbot Microservice:
```bash
cd /var/www/freshers-hub/chatbot-service
git pull
go build -o chatbot-service main.go
systemctl restart chatbot
```
#### 4. Restart Telegram & Discord Bot:
```bash
cd /var/www/freshers-hub/telegram-bot
git pull
systemctl restart telegram-bot
```
---
## 📊 3. Health & Status Check Commands
Want to check if all services are running healthy? Run these commands:
### Check Status of All Services:
```bash
systemctl status springboot chatbot telegram-bot nginx postgresql
```
### Check Active Listening Ports:
```bash
ss -tulpn | grep -E '8085|8081|8082|80|443|5432'
```
*(You should see Java on 8085, Go on 8081, Python on 8082, Nginx on 80/443, PostgreSQL on 5432).*
---
## 📜 4. Viewing Live System Logs (Debugging Errors)
If a feature is failing or you want to see live incoming requests:
### 1. View Spring Boot Backend Logs:
```bash
journalctl -u springboot -n 50 --no-pager
```
*To follow logs live in real-time:* `journalctl -u springboot -f`
### 2. View Go Chatbot Logs:
```bash
journalctl -u chatbot -n 50 --no-pager
```
### 3. View Telegram & Discord Bot Logs:
```bash
journalctl -u telegram-bot -n 50 --no-pager
```
### 4. View Nginx Web Server Access & Error Logs:
```bash
# Recent web traffic requests:
tail -n 30 /var/log/nginx/access.log
# Recent web server errors:
tail -n 30 /var/log/nginx/error.log
```
---
## 🛠️ 5. Common Troubleshooting & Emergency Fixes
### 🚨 Problem 1: "502 Bad Gateway" on Website
* **Cause:** The Spring Boot backend or Go chatbot is stopped.
* **Fix:** Restart all services:
```bash
systemctl restart springboot chatbot nginx
```
---
### 🚨 Problem 2: Nginx Configuration Test Fails
* **Check for Nginx syntax errors:**
```bash
sudo nginx -t
```
* **If it says syntax is OK, reload Nginx:**
```bash
sudo systemctl reload nginx
```
---
### 🚨 Problem 3: Check & Renew SSL Certificates (HTTPS)
* **Check all SSL certificates & expiry dates:**
```bash
sudo certbot certificates
```
* **Force renew SSL certificates (if needed):**
```bash
sudo certbot renew --force-renewal
sudo systemctl reload nginx
```
---
### 🚨 Problem 4: PostgreSQL Database Connection Error
* **Check PostgreSQL status:**
```bash
systemctl status postgresql
```
* **Test Database Password manually:**
```bash
PGPASSWORD='RITHosting123' psql -U postgres -h localhost -d freshers_hub_db -c '\dt'
```
---
### 🚨 Problem 5: Full Server Reboot (Emergency Reset)
If the server becomes completely unresponsive or memory is full:
```bash
sudo reboot
```
*Wait 60 seconds, reconnect via SSH, and check services:*
```bash
systemctl status springboot chatbot telegram-bot nginx postgresql
```
---
## 💾 6. Database Backup & Restore
### Backup PostgreSQL Database to a `.sql` File:
```bash
PGPASSWORD='RITHosting123' pg_dump -U postgres -h localhost freshers_hub_db > /root/backup_freshers_hub_$(date +%F).sql
```
### Restore Database from a `.sql` Backup File:
```bash
PGPASSWORD='RITHosting123' psql -U postgres -h localhost freshers_hub_db < /root/backup_freshers_hub_2026-08-04.sql
```
---
## 🛡️ 7. Systemd Service File Reference Paths
For reference, all auto-start systemd service configuration files are stored at:
1. **Spring Boot Backend:** `/etc/systemd/system/springboot.service`
2. **Go Chatbot Engine:** `/etc/systemd/system/chatbot.service`
3. **Telegram & Discord Bot:** `/etc/systemd/system/telegram-bot.service`
4. **Nginx Web Server Config:** `/etc/nginx/sites-available/default`
---
### 🏆 You're all set!
Save this document for your team to easily maintain, update, and manage the server with zero prior Linux experience.

Binary file not shown.

View File

@@ -1,14 +1,39 @@
package com.rit.portal.dto; package com.rit.portal.dto;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class DriverLocationUpdate { public class DriverLocationUpdate {
private Double latitude; private Double latitude;
private Double longitude; private Double longitude;
private String pin; private String pin;
public DriverLocationUpdate() {}
public DriverLocationUpdate(Double latitude, Double longitude, String pin) {
this.latitude = latitude;
this.longitude = longitude;
this.pin = pin;
}
public Double getLatitude() {
return latitude;
}
public void setLatitude(Double latitude) {
this.latitude = latitude;
}
public Double getLongitude() {
return longitude;
}
public void setLongitude(Double longitude) {
this.longitude = longitude;
}
public String getPin() {
return pin;
}
public void setPin(String pin) {
this.pin = pin;
}
} }

BIN
image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

After

Width:  |  Height:  |  Size: 282 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -1,33 +0,0 @@
plugins {
id 'com.android.application'
}
android {
namespace 'com.rit.driver'
compileSdk 34
defaultConfig {
applicationId "com.rit.driver"
minSdk 21
targetSdk 34
versionCode 1
versionName "1.0"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
}

View File

@@ -1,43 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Location Permissions -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<!-- Foreground Service Permissions -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<!-- Wakelock and Network Permissions -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".TrackingService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="location" />
</application>
</manifest>

View File

@@ -1,9 +0,0 @@
package com.rit.driver;
public class Config {
public static final String BACKEND_URL = "http://10.43.158.201:8085";
public static final String DEFAULT_PIN = "RITDRIVER";
}

View File

@@ -1,202 +0,0 @@
package com.rit.driver;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
private static final int PERMISSION_REQUEST_CODE = 888;
private EditText etRouteNumber;
private EditText etDriverPin;
private Button btnStartSharing;
private Button btnStopSharing;
private TextView tvStatusHeader;
private TextView tvStatusLogs;
private LinearLayout statusCard;
private boolean isServiceRunning = false;
// Receive tracking updates from Foreground Service
private final BroadcastReceiver locationReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
if (intent.hasExtra(TrackingService.EXTRA_STATUS)) {
String status = intent.getStringExtra(TrackingService.EXTRA_STATUS);
tvStatusHeader.setText("STATUS: " + (isServiceRunning ? "ACTIVE" : "INACTIVE"));
logStatus(status);
}
if (intent.hasExtra(TrackingService.EXTRA_LATITUDE) && intent.hasExtra(TrackingService.EXTRA_LONGITUDE)) {
double lat = intent.getDoubleExtra(TrackingService.EXTRA_LATITUDE, 0.0);
double lng = intent.getDoubleExtra(TrackingService.EXTRA_LONGITUDE, 0.0);
float accuracy = intent.getFloatExtra(TrackingService.EXTRA_ACCURACY, 0.0f);
String gpsInfo = String.format("GPS Coords: %.6f, %.6f\nAccuracy: ±%.1fm", lat, lng, accuracy);
logStatus(gpsInfo);
}
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Bind Views
etRouteNumber = findViewById(R.id.etRouteNumber);
etDriverPin = findViewById(R.id.etDriverPin);
etDriverPin.setText(Config.DEFAULT_PIN);
btnStartSharing = findViewById(R.id.btnStartSharing);
btnStopSharing = findViewById(R.id.btnStopSharing);
tvStatusHeader = findViewById(R.id.tvStatusHeader);
tvStatusLogs = findViewById(R.id.tvStatusLogs);
statusCard = findViewById(R.id.statusCard);
btnStartSharing.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (checkAndRequestPermissions()) {
startTracking();
}
}
});
btnStopSharing.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
stopTracking();
}
});
}
@Override
protected void onResume() {
super.onResume();
// Register receiver for service communication
IntentFilter filter = new IntentFilter(TrackingService.ACTION_LOCATION_BROADCAST);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
registerReceiver(locationReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
} else {
registerReceiver(locationReceiver, filter);
}
}
@Override
protected void onPause() {
// Unregister receiver
unregisterReceiver(locationReceiver);
super.onPause();
}
private void startTracking() {
String serverUrl = Config.BACKEND_URL;
String routeNumber = etRouteNumber.getText().toString().trim();
String driverPin = etDriverPin.getText().toString().trim();
if (routeNumber.isEmpty()) {
Toast.makeText(this, "Enter bus route number", Toast.LENGTH_SHORT).show();
return;
}
if (driverPin.isEmpty()) {
Toast.makeText(this, "Enter security driver PIN", Toast.LENGTH_SHORT).show();
return;
}
Intent serviceIntent = new Intent(this, TrackingService.class);
serviceIntent.putExtra("server_url", serverUrl);
serviceIntent.putExtra("route_number", routeNumber);
serviceIntent.putExtra("driver_pin", driverPin);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(serviceIntent);
} else {
startService(serviceIntent);
}
isServiceRunning = true;
btnStartSharing.setVisibility(View.GONE);
btnStopSharing.setVisibility(View.VISIBLE);
tvStatusHeader.setText("STATUS: ACTIVE");
logStatus("Foreground tracking service started.");
}
private void stopTracking() {
Intent serviceIntent = new Intent(this, TrackingService.class);
stopService(serviceIntent);
isServiceRunning = false;
btnStartSharing.setVisibility(View.VISIBLE);
btnStopSharing.setVisibility(View.GONE);
tvStatusHeader.setText("STATUS: INACTIVE");
logStatus("Tracking stopped by driver.");
}
private void logStatus(String msg) {
String currentText = tvStatusLogs.getText().toString();
tvStatusLogs.setText(msg + "\n\n" + currentText);
}
private boolean checkAndRequestPermissions() {
boolean hasFine = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
boolean hasCoarse = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
List<String> listPermissionsNeeded = new ArrayList<>();
if (!hasFine && !hasCoarse) {
listPermissionsNeeded.add(Manifest.permission.ACCESS_FINE_LOCATION);
listPermissionsNeeded.add(Manifest.permission.ACCESS_COARSE_LOCATION);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) {
listPermissionsNeeded.add(Manifest.permission.POST_NOTIFICATIONS);
}
}
if (!listPermissionsNeeded.isEmpty()) {
ActivityCompat.requestPermissions(
this,
listPermissionsNeeded.toArray(new String[0]),
PERMISSION_REQUEST_CODE
);
return false;
}
return true;
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
boolean hasFine = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
boolean hasCoarse = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED;
if (hasFine || hasCoarse) {
startTracking();
} else {
Toast.makeText(this, "Location permission (Precise or Approximate) is required to track the bus.", Toast.LENGTH_LONG).show();
}
}
}
}

View File

@@ -1,77 +0,0 @@
package com.rit.driver;
import android.util.Log;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
public class NetworkHelper {
private static final String TAG = "NetworkHelper";
public interface Callback {
void onSuccess();
void onFailure(String error);
}
public static void postLocation(final String serverUrl, final String routeNumber, final double lat, final double lng, final String pin, final Callback callback) {
new Thread(new Runnable() {
@Override
public void run() {
HttpURLConnection conn = null;
try {
// Clean URL trailing slash and route mapping
String cleanUrl = serverUrl;
if (cleanUrl.endsWith("/")) {
cleanUrl = cleanUrl.substring(0, cleanUrl.length() - 1);
}
String targetUrl = cleanUrl + "/api/bus-locations/" + routeNumber;
URL url = new URL(targetUrl);
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
conn.setConnectTimeout(8000);
conn.setReadTimeout(8000);
// Build JSON string payload
String jsonInputString = String.format(
"{\"latitude\": %f, \"longitude\": %f, \"pin\": \"%s\"}",
lat, lng, pin
);
try (OutputStream os = conn.getOutputStream()) {
byte[] input = jsonInputString.getBytes(StandardCharsets.UTF_8);
os.write(input, 0, input.length);
}
int code = conn.getResponseCode();
if (code == 200 || code == 201) {
Log.d(TAG, "Location uploaded successfully: " + code);
if (callback != null) {
callback.onSuccess();
}
} else {
String errMsg = "HTTP error code " + code;
Log.w(TAG, errMsg);
if (callback != null) {
callback.onFailure(errMsg);
}
}
} catch (Exception e) {
String errMsg = "Upload failed: " + e.getMessage();
Log.e(TAG, errMsg, e);
if (callback != null) {
callback.onFailure(errMsg);
}
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
}).start();
}
}

View File

@@ -1,213 +0,0 @@
package com.rit.driver;
import android.annotation.SuppressLint;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
public class TrackingService extends Service {
private static final String TAG = "TrackingService";
private static final String CHANNEL_ID = "TrackingServiceChannel";
private static final int NOTIFICATION_ID = 444;
private LocationManager locationManager;
private PowerManager.WakeLock wakeLock;
private String serverUrl = "";
private String routeNumber = "";
private String driverPin = "";
// Broadcast action for MainActivity to update UI coordinates
public static final String ACTION_LOCATION_BROADCAST = "com.rit.driver.LOCATION_BROADCAST";
public static final String EXTRA_LATITUDE = "extra_latitude";
public static final String EXTRA_LONGITUDE = "extra_longitude";
public static final String EXTRA_ACCURACY = "extra_accuracy";
public static final String EXTRA_STATUS = "extra_status";
private final LocationListener locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
double lat = location.getLatitude();
double lng = location.getLongitude();
float accuracy = location.getAccuracy();
Log.d(TAG, "Location updated: " + lat + ", " + lng + " (Accuracy: " + accuracy + "m)");
// Broadcast updates locally to MainActivity UI
Intent broadcastIntent = new Intent(ACTION_LOCATION_BROADCAST);
broadcastIntent.putExtra(EXTRA_LATITUDE, lat);
broadcastIntent.putExtra(EXTRA_LONGITUDE, lng);
broadcastIntent.putExtra(EXTRA_ACCURACY, accuracy);
broadcastIntent.putExtra(EXTRA_STATUS, "Broadcasting Location...");
sendBroadcast(broadcastIntent);
// Upload location to backend
NetworkHelper.postLocation(serverUrl, routeNumber, lat, lng, driverPin, new NetworkHelper.Callback() {
@Override
public void onSuccess() {
Log.d(TAG, "Uploaded location successfully");
Intent statusIntent = new Intent(ACTION_LOCATION_BROADCAST);
statusIntent.putExtra(EXTRA_STATUS, "Upload Success: Location synced.");
sendBroadcast(statusIntent);
}
@Override
public void onFailure(String error) {
Log.e(TAG, "Upload failed: " + error);
Intent statusIntent = new Intent(ACTION_LOCATION_BROADCAST);
statusIntent.putExtra(EXTRA_STATUS, "Upload Failed: " + error);
sendBroadcast(statusIntent);
}
});
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {}
@Override
public void onProviderEnabled(String provider) {}
@Override
public void onProviderDisabled(String provider) {
Intent statusIntent = new Intent(ACTION_LOCATION_BROADCAST);
statusIntent.putExtra(EXTRA_STATUS, "GPS Provider Disabled. Please turn on Location/GPS.");
sendBroadcast(statusIntent);
}
};
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "onCreate: Service starting");
createNotificationChannel();
}
@SuppressLint("InvalidWakeLockTag")
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand: Service active");
if (intent != null) {
serverUrl = intent.getStringExtra("server_url");
routeNumber = intent.getStringExtra("route_number");
driverPin = intent.getStringExtra("driver_pin");
}
// 1. Show Foreground Notification
Intent notificationIntent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(
this, 0, notificationIntent,
PendingIntent.FLAG_IMMUTABLE
);
Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("RIT Driver Tracker")
.setContentText("Active Journey: Route " + routeNumber + " is currently tracking...")
.setSmallIcon(android.R.drawable.ic_menu_compass)
.setContentIntent(pendingIntent)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.build();
startForeground(NOTIFICATION_ID, notification);
// 2. Acquire Power CPU WakeLock
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
if (pm != null) {
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "TrackingService::WakeLock");
wakeLock.acquire();
Log.d(TAG, "WakeLock acquired successfully");
}
// 3. Register Location Listeners (both GPS and Network for indoors/outdoors)
locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
try {
if (locationManager != null) {
// Request GPS updates
if (locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER,
5000,
0.0f,
locationListener
);
}
// Request Network/Wi-Fi location updates (essential indoors)
if (locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
locationManager.requestLocationUpdates(
LocationManager.NETWORK_PROVIDER,
5000,
0.0f,
locationListener
);
}
// Immediately trigger initial fix if available
Location lastGps = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
Location lastNet = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
Location bestLocation = (lastGps != null) ? lastGps : lastNet;
if (bestLocation != null) {
locationListener.onLocationChanged(bestLocation);
}
Log.d(TAG, "Location Listeners registered successfully");
}
} catch (SecurityException e) {
Log.e(TAG, "SecurityException: Location permissions not granted", e);
stopSelf();
}
return START_REDELIVER_INTENT;
}
@Override
public void onDestroy() {
Log.d(TAG, "onDestroy: Stopping service cleanups");
// 1. Remove GPS Listener
if (locationManager != null) {
locationManager.removeUpdates(locationListener);
}
// 2. Release CPU WakeLock
if (wakeLock != null && wakeLock.isHeld()) {
wakeLock.release();
Log.d(TAG, "WakeLock released cleanly");
}
super.onDestroy();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel serviceChannel = new NotificationChannel(
CHANNEL_ID,
"RIT Driver Tracker Channel",
NotificationManager.IMPORTANCE_DEFAULT
);
NotificationManager manager = getSystemService(NotificationManager.class);
if (manager != null) {
manager.createNotificationChannel(serviceChannel);
}
}
}
}

View File

@@ -1,12 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:fillColor="#1E293B"
android:pathData="M0,0h108v108h-108z" />
<path
android:fillColor="#F97316"
android:pathData="M54,20 C35.22,20 20,35.22 20,54 C20,72.78 35.22,88 54,88 C72.78,88 88,72.78 88,54 C88,35.22 72.78,20 54,20 Z M54,76 C41.85,76 32,66.15 32,54 C32,41.85 41.85,32 54,32 C66.15,32 76,41.85 76,54 C76,66.15 66.15,76 54,76 Z M54,40 L60,54 L54,68 L48,54 Z" />
</vector>

View File

@@ -1,148 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:background="#1E293B">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="24dp">
<!-- Title -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="RIT Driver Tracker"
android:textColor="#FFFFFF"
android:textSize="28sp"
android:textStyle="bold"
android:gravity="center"
android:layout_marginTop="24dp"
android:layout_marginBottom="8dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Live Background Geolocation Broadcaster"
android:textColor="#94A3B8"
android:textSize="12sp"
android:gravity="center"
android:layout_marginBottom="32dp" />
<!-- Route Number Input -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="BUS ROUTE NUMBER"
android:textColor="#94A3B8"
android:textSize="11sp"
android:textStyle="bold"
android:layout_marginBottom="6dp" />
<EditText
android:id="@+id/etRouteNumber"
android:layout_width="match_parent"
android:layout_height="52dp"
android:background="@android:drawable/editbox_background"
android:hint="R01"
android:text="R01"
android:inputType="textCapCharacters"
android:padding="12dp"
android:textSize="14sp"
android:layout_marginBottom="20dp" />
<!-- Driver PIN Input -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="DRIVER SECURITY PIN"
android:textColor="#94A3B8"
android:textSize="11sp"
android:textStyle="bold"
android:layout_marginBottom="6dp" />
<EditText
android:id="@+id/etDriverPin"
android:layout_width="match_parent"
android:layout_height="52dp"
android:background="@android:drawable/editbox_background"
android:hint="Enter Security PIN"
android:text="RITDRIVER"
android:inputType="textPassword"
android:padding="12dp"
android:textSize="14sp"
android:layout_marginBottom="32dp" />
<!-- Start Sharing Button -->
<Button
android:id="@+id/btnStartSharing"
android:layout_width="match_parent"
android:layout_height="56dp"
android:backgroundTint="#F97316"
android:text="START LOCATION SHARING"
android:textColor="#FFFFFF"
android:textStyle="bold"
android:textSize="15sp"
android:layout_marginBottom="16dp" />
<!-- Stop Sharing Button -->
<Button
android:id="@+id/btnStopSharing"
android:layout_width="match_parent"
android:layout_height="56dp"
android:backgroundTint="#EF4444"
android:text="STOP LOCATION SHARING"
android:textColor="#FFFFFF"
android:textStyle="bold"
android:textSize="15sp"
android:visibility="gone"
android:layout_marginBottom="24dp" />
<!-- Status Card -->
<LinearLayout
android:id="@+id/statusCard"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#0F172A"
android:padding="16dp"
android:visibility="visible">
<TextView
android:id="@+id/tvStatusHeader"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="STATUS: INACTIVE"
android:textColor="#94A3B8"
android:textStyle="bold"
android:textSize="12sp"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/tvStatusLogs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Toggle location sharing to begin tracking.\nKeep phone screen turned on or off — the app will broadcast fine coordinates in the background."
android:textColor="#64748B"
android:textSize="12sp"
android:lineSpacingExtra="3dp" />
</LinearLayout>
<!-- Keep screen notice -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Notice: Once active, you can lock the screen or close the app interface. The native system foreground notification service keeps tracking alive."
android:textColor="#64748B"
android:textSize="10sp"
android:gravity="center"
android:layout_marginTop="24dp"
android:layout_marginBottom="24dp" />
</LinearLayout>
</ScrollView>

View File

@@ -1,3 +0,0 @@
<resources>
<string name="app_name">RIT Driver Tracker</string>
</resources>

View File

@@ -1,4 +0,0 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '8.7.2' apply false
}

View File

@@ -1,4 +0,0 @@
# Project-wide Gradle settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
android.useAndroidX=true
android.enableJetifier=true

Binary file not shown.

View File

@@ -1,7 +0,0 @@
#Fri Jul 24 14:49:37 IST 2026
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=d725d707bfabd4dfdc958c624003b3c80accc03f7037b5122c4b1d0ef15cecab
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

248
rit-driver-app/gradlew vendored
View File

@@ -1,248 +0,0 @@
#!/bin/sh
#
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

View File

@@ -1,93 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

View File

@@ -1,8 +0,0 @@
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Fri Jul 24 14:24:21 IST 2026
sdk.dir=C\:\\Users\\dorut\\AppData\\Local\\Android\\Sdk

View File

@@ -1,17 +0,0 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "RIT Driver Tracker"
include ':app'

BIN
src/assets/map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

BIN
src/assets/map.png.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

View File

@@ -187,7 +187,8 @@ export default function BusRouteMap({ selectedRoute, allRoutes }: BusRouteMapPro
/> />
{Object.entries(allLiveLocations).map(([rNum, loc]) => { {Object.entries(allLiveLocations).map(([rNum, loc]) => {
if (selectedRoute && selectedRoute.number !== rNum) return null; const normalizeRouteNum = (str: string) => (str || '').replace(/^[Rr]0*/, '').trim().toUpperCase();
if (selectedRoute && normalizeRouteNum(selectedRoute.number) !== normalizeRouteNum(rNum)) return null;
const isStale = loc.stopped || (loc.lastUpdated ? (new Date().getTime() - new Date(loc.lastUpdated).getTime() > 120000) : false); const isStale = loc.stopped || (loc.lastUpdated ? (new Date().getTime() - new Date(loc.lastUpdated).getTime() > 120000) : false);

View File

@@ -1,25 +1,16 @@
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { GraduationCap, MapPin, Phone, Mail, Camera, Send, Globe, Play, ArrowRight } from 'lucide-react'; import { GraduationCap, MapPin, Phone, Mail, ArrowRight } from 'lucide-react';
const quickLinks = [ const quickLinks = [
{ label: 'Home', path: '/' }, { label: 'Home', path: '/' },
{ label: 'Notes & PYQs', path: '/notes' }, { label: 'Notes & PYQs', path: '/notes' },
{ label: 'Chatbot', path: '/ai-assistant' }, { label: 'RIT Chatbot', path: '/ai-assistant' },
{ label: 'Campus Map', path: '/campus' }, { label: '3D Campus Map', path: '/campus' },
{ label: 'Bus Routes', path: '/bus-routes' }, { label: 'Bus Routes', path: '/bus-routes' },
{ label: 'Faculty Directory', path: '/faculty' }, { label: 'Faculty Directory', path: '/faculty' },
{ label: 'Events', path: '/events' }, { label: 'Events', path: '/events' },
{ label: 'Community', path: '/community' }, { label: 'Community', path: '/community' },
{ label: 'Dev Collab', path: '/collab' }, { label: 'Dev Hub', path: '/collab' },
];
const resources = [
{ label: 'Student Handbook', path: '#' },
{ label: 'Academic Calendar', path: '#' },
{ label: 'ERP Portal', path: '#' },
{ label: 'Library Portal', path: '#' },
{ label: 'Exam Results', path: '#' },
{ label: 'Fee Payment', path: '#' },
]; ];
export default function Footer() { export default function Footer() {
@@ -49,7 +40,7 @@ export default function Footer() {
{/* Main Footer */} {/* Main Footer */}
<div className="container-custom py-14"> <div className="container-custom py-14">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-10"> <div className="grid grid-cols-1 md:grid-cols-3 gap-10">
{/* Brand */} {/* Brand */}
<div> <div>
<div className="flex items-center gap-2.5 mb-4"> <div className="flex items-center gap-2.5 mb-4">
@@ -65,68 +56,34 @@ export default function Footer() {
</div> </div>
</div> </div>
<p className="text-slate-400 text-sm leading-relaxed mb-5" style={{ fontFamily: 'Inter, sans-serif' }}> <p className="text-slate-400 text-sm leading-relaxed mb-5" style={{ fontFamily: 'Inter, sans-serif' }}>
Your all-in-one guide to campus life at RIT. Notes, chatbot, events, faculty, and more made for freshers. Your all-in-one guide to campus life at RIT. Notes, chatbot, bus routes, 3D campus map, and developer hub built for students.
</p> </p>
<div className="flex items-center gap-3">
{[Camera, Send, Globe, Play].map((Icon, i) => (
<a
key={i}
href="#"
className="w-9 h-9 rounded-lg bg-slate-700 flex items-center justify-center text-slate-400 hover:bg-[#F97316] hover:text-white transition-all duration-200"
aria-label="Social link"
>
<Icon className="w-4 h-4" />
</a>
))}
</div>
</div> </div>
{/* Quick Links */} {/* Quick Links */}
<div> <div>
<h4 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}> <h4 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}>
Quick Links Quick Navigation
</h4> </h4>
<ul className="space-y-2.5"> <div className="grid grid-cols-2 gap-2">
{quickLinks.map((link) => ( {quickLinks.map((link) => (
<li key={link.path}>
<Link <Link
key={link.path}
to={link.path} to={link.path}
className="text-slate-400 hover:text-[#F97316] text-sm transition-colors flex items-center gap-1.5 group" className="text-slate-400 hover:text-[#F97316] text-sm transition-colors flex items-center gap-1.5 group py-1"
style={{ fontFamily: 'Inter, sans-serif' }} style={{ fontFamily: 'Inter, sans-serif' }}
> >
<span className="w-1 h-1 rounded-full bg-[#F97316] opacity-0 group-hover:opacity-100 transition-opacity" /> <span className="w-1 h-1 rounded-full bg-[#F97316] opacity-0 group-hover:opacity-100 transition-opacity" />
{link.label} {link.label}
</Link> </Link>
</li>
))} ))}
</ul>
</div> </div>
{/* Resources */}
<div>
<h4 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}>
Resources
</h4>
<ul className="space-y-2.5">
{resources.map((res) => (
<li key={res.label}>
<a
href={res.path}
className="text-slate-400 hover:text-[#F97316] text-sm transition-colors flex items-center gap-1.5 group"
style={{ fontFamily: 'Inter, sans-serif' }}
>
<span className="w-1 h-1 rounded-full bg-[#F97316] opacity-0 group-hover:opacity-100 transition-opacity" />
{res.label}
</a>
</li>
))}
</ul>
</div> </div>
{/* Contact */} {/* Contact */}
<div> <div>
<h4 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}> <h4 className="text-white font-semibold mb-4 text-sm uppercase tracking-wider" style={{ fontFamily: 'Poppins, sans-serif' }}>
Contact Campus Location & Contact
</h4> </h4>
<ul className="space-y-3"> <ul className="space-y-3">
<li className="flex items-start gap-3 text-slate-400 text-sm" style={{ fontFamily: 'Inter, sans-serif' }}> <li className="flex items-start gap-3 text-slate-400 text-sm" style={{ fontFamily: 'Inter, sans-serif' }}>

View File

@@ -0,0 +1,84 @@
import { useEffect, useRef, useState } from 'react';
interface LazyVideoHeroProps {
children?: React.ReactNode;
}
export default function LazyVideoHero({ children }: LazyVideoHeroProps) {
const containerRef = useRef<HTMLDivElement>(null);
const videoRef = useRef<HTMLVideoElement>(null);
const [isIntersecting, setIsIntersecting] = useState(true);
const [isVideoPlaying, setIsVideoPlaying] = useState(false);
useEffect(() => {
if (videoRef.current) {
videoRef.current.muted = true;
videoRef.current.play().catch(() => {});
}
const container = containerRef.current;
if (!container) return;
const observer = new IntersectionObserver(
([entry]) => {
setIsIntersecting(entry.isIntersecting);
if (videoRef.current) {
if (entry.isIntersecting) {
videoRef.current.muted = true;
videoRef.current.play().catch(() => {});
} else {
videoRef.current.pause();
}
}
},
{ threshold: 0.1 }
);
observer.observe(container);
return () => {
observer.disconnect();
};
}, []);
return (
<div ref={containerRef} className="relative w-full overflow-hidden">
{/* ─── Optimized Video Background ────────────────────────────────────────── */}
<div
className="absolute inset-0 w-full h-full z-0 pointer-events-none"
style={{ contain: 'strict', willChange: 'transform' }}
>
<video
ref={videoRef}
autoPlay
muted
loop
playsInline
preload="auto"
className="w-full h-full object-cover transition-opacity duration-500"
style={{
filter: 'contrast(1.1) brightness(0.85) saturate(1.1)',
opacity: isIntersecting ? 1 : 0,
contain: 'strict',
}}
>
<source src="/video/whatsapp-video.mp4" type="video/mp4" />
<source src="/video/whatsapp-video.webm" type="video/webm" />
</video>
{/* High-Contrast Glassmorphic Overlay Gradient */}
<div
className="absolute inset-0"
style={{
background: 'linear-gradient(to bottom, rgba(15, 23, 42, 0.5) 0%, rgba(15, 23, 42, 0.8) 100%)',
}}
/>
</div>
{/* ─── Hero Content ──────────────────────────────────────────────────────── */}
<div className="relative z-10">
{children}
</div>
</div>
);
}

View File

@@ -1,6 +1,6 @@
import { useState, useRef, useEffect } from 'react'; import { useState, useRef, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { Bot, Send, Sparkles, RefreshCw, Mic, X, ChevronRight } from 'lucide-react'; import { Bot, Send, Sparkles, RefreshCw, X, ChevronRight, MessageSquare, ShieldCheck, Zap } from 'lucide-react';
import { AI_SUGGESTED_PROMPTS } from '@/constants'; import { AI_SUGGESTED_PROMPTS } from '@/constants';
import type { ChatMessage } from '@/types'; import type { ChatMessage } from '@/types';
import { getChatbotUrl } from '@/lib/utils'; import { getChatbotUrl } from '@/lib/utils';
@@ -9,7 +9,7 @@ const INITIAL_MESSAGES: ChatMessage[] = [
{ {
id: '1', id: '1',
role: 'assistant', role: 'assistant',
content: "👋 Hi there! I'm your **RIT Chatbot**. I have comprehensive knowledge about Rajalakshmi Institute of Technology — from admission procedures to campus facilities.\n\nHow can I help you today?", content: "👋 Hi there! I'm your **RIT Chatbot**.\n\n⚠ *Please note: Answers generated by AI may occasionally be inaccurate or wrong. Please cross-reference critical academic, exam, or administrative details with official campus sources.*\n\nHow can I help you today?",
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}, },
]; ];
@@ -42,7 +42,8 @@ export default function AIAssistant() {
}; };
setMessages((prev) => [...prev, userMsg]); setMessages((prev) => [...prev, userMsg]);
setInput(''); setInput('');
setIsTyping(true); // Call the Go chatbot microservice setIsTyping(true);
let responseText = ''; let responseText = '';
try { try {
const res = await fetch(getChatbotUrl('/api/chat'), { const res = await fetch(getChatbotUrl('/api/chat'), {
@@ -73,7 +74,7 @@ export default function AIAssistant() {
} else if (lower.includes('library')) { } else if (lower.includes('library')) {
responseText = responses.library; responseText = responses.library;
} else { } else {
responseText = `Thank you for your question about **"${text}"**!\n\nI couldn't reach the chatbot API server. Please make sure the Go service is running on ${getChatbotUrl()}.`; responseText = `Thank you for your question about **"${text}"**!\n\nI couldn't reach the chatbot API server. Please make sure the Go service is running.`;
} }
} }
@@ -100,103 +101,103 @@ export default function AIAssistant() {
}; };
return ( return (
<div className="min-h-screen flex flex-col" style={{ backgroundColor: '#FAFAFA' }}> <div className="min-h-screen flex flex-col bg-[#FAFAFA]">
{/* Header */} {/* Header */}
<div className="bg-white border-b border-[#E5E7EB] py-4 relative z-10"> <div className="bg-white border-b border-[#E5E7EB] py-4 relative z-10 shadow-xs">
<div className="container-custom flex items-center justify-between"> <div className="container-custom flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}> <div className="w-10 h-10 rounded-xl flex items-center justify-center shadow-sm" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
<Bot className="w-5 h-5 text-white" /> <Bot className="w-5.5 h-5.5 text-white" />
</div> </div>
<div> <div>
<h1 className="font-semibold text-[#1E293B] text-sm" style={{ fontFamily: 'Poppins, sans-serif' }}>RIT Chatbot</h1> <h1 className="font-bold text-[#1E293B] text-base" style={{ fontFamily: 'Poppins, sans-serif' }}>RIT Assistant</h1>
<div className="flex items-center gap-1.5 text-xs text-[#94A3B8]"> <div className="flex items-center gap-1.5 text-xs text-[#64748B]">
<span className="w-1.5 h-1.5 rounded-full bg-emerald-400" /> <span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" />
Campus Knowledge Enabled Campus Knowledge Engine Online
</div> </div>
</div> </div>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-2 sm:gap-3">
<a <a
href="https://t.me/Ritchatbot_bot" href="https://t.me/Ritchatbot_bot"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-semibold text-white transition-all shadow-sm hover:brightness-105 active:scale-95" className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-medium text-white transition-all shadow-xs hover:brightness-105 active:scale-95"
style={{ style={{
background: 'linear-gradient(135deg, #0088cc, #24A1DE)', background: 'linear-gradient(135deg, #0088cc, #24A1DE)',
fontFamily: 'Inter, sans-serif' fontFamily: 'Inter, sans-serif'
}} }}
> >
<Send className="w-3.5 h-3.5" /> <Send className="w-3.5 h-3.5" />
Use on Telegram <span className="hidden sm:inline">Telegram Bot</span>
</a> </a>
<a <a
href="https://discord.com/api/oauth2/authorize?client_id=1476942599167414506&permissions=8&scope=bot" href="https://discord.com/api/oauth2/authorize?client_id=1476942599167414506&permissions=8&scope=bot"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-semibold text-white transition-all shadow-sm hover:brightness-105 active:scale-95" className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-medium text-white transition-all shadow-xs hover:brightness-105 active:scale-95"
style={{ style={{
background: 'linear-gradient(135deg, #5865F2, #7289DA)', background: 'linear-gradient(135deg, #5865F2, #7289DA)',
fontFamily: 'Inter, sans-serif' fontFamily: 'Inter, sans-serif'
}} }}
> >
<Bot className="w-3.5 h-3.5" /> <Bot className="w-3.5 h-3.5" />
Use on Discord <span className="hidden sm:inline">Discord Bot</span>
</a> </a>
<motion.button <motion.button
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.97 }}
onClick={clearChat} onClick={clearChat}
className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs text-[#94A3B8] hover:text-[#475569] hover:bg-gray-100 transition-all border border-[#E5E7EB] bg-white" className="flex items-center gap-1.5 px-3 py-2 rounded-xl text-xs font-medium text-[#64748B] hover:text-[#1E293B] hover:bg-slate-100 transition-all border border-[#E5E7EB] bg-white shadow-2xs"
> >
<RefreshCw className="w-3.5 h-3.5" /> <RefreshCw className="w-3.5 h-3.5" />
New Chat <span>Reset</span>
</motion.button> </motion.button>
</div> </div>
</div> </div>
</div> </div>
<div className="flex-1 container-custom py-6 flex flex-col gap-4 max-w-4xl mx-auto w-full"> <div className="flex-1 container-custom py-6 flex flex-col gap-4 max-w-4xl mx-auto w-full">
{/* Features strip */} {/* Sleek Badge Strip */}
<motion.div <motion.div
initial={{ opacity: 0, y: 10 }} initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
className="flex flex-wrap gap-2" className="flex flex-wrap gap-2"
> >
{[ {[
{ icon: Sparkles, label: 'Gemini AI' }, { icon: Zap, label: 'Instant Responses' },
{ icon: Bot, label: 'RAG Enabled' }, { icon: ShieldCheck, label: 'Verified Campus Info' },
{ icon: ChevronRight, label: 'Campus Knowledge' }, { icon: MessageSquare, label: '24/7 Assistance' },
].map((item, i) => ( ].map((item, i) => (
<div key={i} className="flex items-center gap-1.5 px-3 py-1.5 rounded-full bg-white border border-[#E5E7EB] text-xs text-[#475569]"> <div key={i} className="flex items-center gap-1.5 px-3.5 py-1.5 rounded-full bg-white border border-[#E5E7EB] text-xs font-medium text-[#475569] shadow-2xs">
<item.icon className="w-3.5 h-3.5 text-[#F97316]" /> <item.icon className="w-3.5 h-3.5 text-[#F97316]" />
{item.label} {item.label}
</div> </div>
))} ))}
</motion.div> </motion.div>
{/* Messages */} {/* Chat Container */}
<div <div
className="flex-1 bg-white rounded-3xl border border-[#E5E7EB] p-6 flex flex-col gap-4 overflow-y-auto" className="flex-1 bg-white rounded-3xl border border-[#E5E7EB] p-4 sm:p-6 flex flex-col gap-4 overflow-y-auto"
style={{ minHeight: '400px', maxHeight: '65vh', boxShadow: '0 4px 25px -5px rgba(0,0,0,0.08)' }} style={{ minHeight: '420px', maxHeight: '65vh', boxShadow: '0 8px 30px -6px rgba(0,0,0,0.04)' }}
> >
{messages.map((msg) => ( {messages.map((msg) => (
<motion.div <motion.div
key={msg.id} key={msg.id}
initial={{ opacity: 0, y: 12 }} initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }} transition={{ duration: 0.25 }}
className={`flex gap-3 ${msg.role === 'user' ? 'flex-row-reverse' : ''}`} className={`flex gap-3 ${msg.role === 'user' ? 'flex-row-reverse' : ''}`}
> >
{msg.role === 'assistant' && ( {msg.role === 'assistant' && (
<div className="w-8 h-8 rounded-xl flex items-center justify-center shrink-0 mt-1" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}> <div className="w-8 h-8 rounded-xl flex items-center justify-center shrink-0 mt-1 shadow-xs" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
<Bot className="w-4 h-4 text-white" /> <Bot className="w-4 h-4 text-white" />
</div> </div>
)} )}
<div <div
className="max-w-[80%] px-4 py-3 text-sm leading-relaxed" className="max-w-[85%] sm:max-w-[78%] px-4 py-3 text-sm leading-relaxed"
style={{ style={{
background: msg.role === 'user' ? 'linear-gradient(135deg, #F97316, #FB923C)' : '#F8FAFC', background: msg.role === 'user' ? 'linear-gradient(135deg, #F97316, #EA580C)' : '#F8FAFC',
color: msg.role === 'user' ? 'white' : '#1E293B', color: msg.role === 'user' ? 'white' : '#1E293B',
borderRadius: msg.role === 'user' ? '20px 20px 4px 20px' : '20px 20px 20px 4px', borderRadius: msg.role === 'user' ? '20px 20px 4px 20px' : '20px 20px 20px 4px',
border: msg.role === 'assistant' ? '1px solid #E5E7EB' : 'none', border: msg.role === 'assistant' ? '1px solid #E5E7EB' : 'none',
@@ -207,7 +208,7 @@ export default function AIAssistant() {
</motion.div> </motion.div>
))} ))}
{/* Typing */} {/* Typing Indicator */}
<AnimatePresence> <AnimatePresence>
{isTyping && ( {isTyping && (
<motion.div <motion.div
@@ -216,16 +217,16 @@ export default function AIAssistant() {
exit={{ opacity: 0 }} exit={{ opacity: 0 }}
className="flex gap-3" className="flex gap-3"
> >
<div className="w-8 h-8 rounded-xl flex items-center justify-center shrink-0" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}> <div className="w-8 h-8 rounded-xl flex items-center justify-center shrink-0 shadow-xs" style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
<Bot className="w-4 h-4 text-white" /> <Bot className="w-4 h-4 text-white" />
</div> </div>
<div className="flex items-center gap-1 px-4 py-3 bg-[#F8FAFC] rounded-2xl border border-[#E5E7EB]"> <div className="flex items-center gap-1.5 px-4 py-3 bg-[#F8FAFC] rounded-2xl border border-[#E5E7EB]">
{[0, 1, 2].map((i) => ( {[0, 1, 2].map((i) => (
<motion.span <motion.span
key={i} key={i}
className="w-2 h-2 bg-[#94A3B8] rounded-full" className="w-2 h-2 bg-[#F97316] rounded-full"
animate={{ y: [0, -5, 0] }} animate={{ y: [0, -5, 0] }}
transition={{ duration: 0.8, repeat: Infinity, delay: i * 0.15 }} transition={{ duration: 0.6, repeat: Infinity, delay: i * 0.15 }}
/> />
))} ))}
</div> </div>
@@ -240,10 +241,10 @@ export default function AIAssistant() {
{AI_SUGGESTED_PROMPTS.slice(0, 4).map((prompt, i) => ( {AI_SUGGESTED_PROMPTS.slice(0, 4).map((prompt, i) => (
<motion.button <motion.button
key={i} key={i}
whileHover={{ scale: 1.02, backgroundColor: '#FFF7ED' }} whileHover={{ scale: 1.02, backgroundColor: '#FFF7ED', borderColor: '#FED7AA' }}
whileTap={{ scale: 0.98 }} whileTap={{ scale: 0.98 }}
onClick={() => sendMessage(prompt)} onClick={() => sendMessage(prompt)}
className="px-3.5 py-2 rounded-xl border border-[#E5E7EB] text-xs text-[#475569] bg-white transition-all hover:border-[#F97316] hover:text-[#F97316]" className="px-3.5 py-2 rounded-xl border border-[#E5E7EB] text-xs text-[#475569] bg-white transition-all hover:text-[#F97316] shadow-2xs font-medium"
style={{ fontFamily: 'Inter, sans-serif' }} style={{ fontFamily: 'Inter, sans-serif' }}
> >
{prompt} {prompt}
@@ -251,16 +252,16 @@ export default function AIAssistant() {
))} ))}
</div> </div>
{/* Input */} {/* Input Field */}
<div className="bg-white rounded-2xl border border-[#E5E7EB] p-3 flex items-center gap-3" style={{ boxShadow: '0 4px 20px -4px rgba(0,0,0,0.08)' }}> <div className="bg-white rounded-2xl border border-[#E5E7EB] p-2.5 flex items-center gap-3 shadow-sm focus-within:border-[#F97316] transition-all">
<input <input
ref={inputRef} ref={inputRef}
type="text" type="text"
value={input} value={input}
onChange={(e) => setInput(e.target.value)} onChange={(e) => setInput(e.target.value)}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}
placeholder="Ask anything about RIT..." placeholder="Ask anything about RIT campus, routes, hostel, or exams..."
className="flex-1 text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none bg-transparent" className="flex-1 text-sm text-[#1E293B] placeholder-[#94A3B8] focus:outline-none bg-transparent pl-2"
style={{ fontFamily: 'Inter, sans-serif' }} style={{ fontFamily: 'Inter, sans-serif' }}
/> />
{input && ( {input && (
@@ -274,22 +275,20 @@ export default function AIAssistant() {
<X className="w-4 h-4" /> <X className="w-4 h-4" />
</motion.button> </motion.button>
)} )}
<button className="w-9 h-9 rounded-xl flex items-center justify-center text-[#94A3B8] hover:bg-gray-100 transition-all">
<Mic className="w-4.5 h-4.5" />
</button>
<motion.button <motion.button
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.95 }}
onClick={() => sendMessage(input)} onClick={() => sendMessage(input)}
disabled={!input.trim()} disabled={!input.trim()}
className="w-10 h-10 rounded-xl flex items-center justify-center text-white transition-all disabled:opacity-50" className="w-10 h-10 rounded-xl flex items-center justify-center text-white transition-all disabled:opacity-40 shadow-xs cursor-pointer"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }} style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}
> >
<Send className="w-4 h-4" /> <Send className="w-4 h-4" />
</motion.button> </motion.button>
</div> </div>
<p className="text-[11px] text-center text-[#94A3B8] mt-3" style={{ fontFamily: 'Inter, sans-serif' }}>
Chatbot responses may not always be accurate. Please cross-reference critical academic or administrative details with official sources. <p className="text-[11px] text-center text-[#94A3B8]" style={{ fontFamily: 'Inter, sans-serif' }}>
RIT Assistant provides official campus guidance. For administrative requests, visit the department block.
</p> </p>
</div> </div>
</div> </div>

View File

@@ -32,34 +32,39 @@ const BLUEPRINT_LEGEND = [
]; ];
const LOCATION_SPOTS: LocationSpot[] = [ const LOCATION_SPOTS: LocationSpot[] = [
{ name: 'Girls Hostel', x: 8.2, y: 12.0, category: 'Hostel / Mess', desc: 'Residential Quarter for Women', area: 'Top Left' }, { name: 'Girls Hostel', x: 16.0, y: 8.5, category: 'Hostel / Mess', desc: 'Residential Quarter for Women', area: 'Top Left' },
{ name: 'Playground', x: 32.5, y: 19.5, category: 'Amenity', desc: 'Main Sports & Athletic Field', area: 'Top Left' }, { name: 'Playground', x: 40.0, y: 11.0, category: 'Amenity', desc: 'Main Sports & Athletic Field', area: 'Top Center' },
{ name: 'Canteen', x: 63.5, y: 13.5, category: 'Amenity', desc: 'Food Court & Refreshments', area: 'North Center' }, { name: 'Canteen', x: 67.0, y: 8.5, category: 'Amenity', desc: 'Food Court & Refreshments', area: 'Top Right' },
{ name: 'Statue', x: 61.0, y: 26.0, category: 'Amenity', desc: 'Landmark Monument', area: 'North Center' }, { name: 'Steve Jobs Block', x: 16.0, y: 21.0, category: 'Admin / Facility', desc: 'Auditorium, Computer Labs & Staff Rooms', area: 'West Wing' },
{ name: 'Boys Mess', x: 87.8, y: 10.5, category: 'Amenity', desc: 'Ground Floor Dining', area: 'Top Right' }, { name: 'Girls Mess', x: 14.5, y: 31.0, category: 'Hostel / Mess', desc: 'Dedicated Dining Hall for Women', area: 'West Wing' },
{ name: 'Staff Mess', x: 87.8, y: 22.0, category: 'Amenity', desc: 'First Floor Dining', area: 'Top Right' }, { name: 'MEC Lab', x: 14.0, y: 39.5, category: 'Lab / Academic', desc: 'Mechanical Engineering Laboratory', area: 'West Wing' },
{ name: 'C Block', x: 87.8, y: 41.0, category: 'C Block', desc: 'Labs, Classrooms, Auditorium & Staff Rooms', area: 'East Block' }, { name: 'EPL Lab', x: 13.0, y: 47.5, category: 'Lab / Academic', desc: 'Engineering Practices Laboratory', area: 'West Wing' },
{ name: 'Steve Jobs Block', x: 8.1, y: 38.5, category: 'Admin / Facility', desc: 'Auditorium, Labs & Staff Rooms', area: 'West Wing' }, { name: 'Nikola Tesla Block', x: 40.5, y: 36.5, category: 'B Block', desc: 'Main Electrical & Engineering Research Block', area: 'Central Block' },
{ name: 'Girls Mess', x: 8.1, y: 53.0, category: 'Amenity', desc: 'Dedicated Dining Hall for Women', area: 'West Wing' }, { name: 'Astronaut Statue', x: 51.0, y: 23.0, category: 'Amenity', desc: 'Iconic Landmark Monument', area: 'Central Park' },
{ name: 'EPL Lab', x: 8.1, y: 65.5, category: 'Lab / Academic', desc: 'Engineering Practices Laboratory', area: 'South West' }, { name: 'Campus Centre', x: 67.0, y: 23.0, category: 'Admin / Facility', desc: 'Student Activity & Administrative Center', area: 'North East' },
{ name: 'B Block', x: 32.5, y: 55.0, category: 'B Block', desc: 'Labs, Classrooms & Faculty Rooms', area: 'Central West' }, { name: 'Hut', x: 70.0, y: 37.0, category: 'Amenity', desc: 'Student Discussion & Rest Area', area: 'Central East' },
{ name: 'Hut', x: 62.7, y: 42.5, category: 'Amenity', desc: 'Student Discussion & Rest Area', area: 'Central Green' }, { name: 'Homi Jehangir Bhabha Block', x: 87.0, y: 24.0, category: 'A Block', desc: 'Science & Physics Research Block', area: 'East Wing' },
{ name: 'A Block', x: 70.3, y: 67.0, category: 'A Block', desc: 'Labs, Classrooms, Faculty Rooms, Principal Room & Exam Cell', area: 'Central East' }, { name: 'Boys Mess', x: 87.0, y: 17.5, category: 'Hostel / Mess', desc: 'Ground Floor Dining Hall for Men (Homi Bhabha Block)', area: 'East Wing' },
{ name: 'RSB Block', x: 24.0, y: 81.5, category: 'Amenity', desc: 'Library, Auditorium & Accounts Room', area: 'South West' }, { name: 'Staff Mess', x: 87.0, y: 30.5, category: 'Amenity', desc: 'First Floor Dining Hall for Faculty & Staff (Homi Bhabha Block)', area: 'East Wing' },
{ name: 'Side Parking', x: 5.5, y: 85.5, category: 'Parking Area', desc: 'Visitor & Staff Parking Zone', area: 'South West' }, { name: 'C Block', x: 87.0, y: 38.0, category: 'C Block', desc: 'Labs, Classrooms & Staff Rooms', area: 'East Wing' },
{ name: 'Arcade', x: 41.5, y: 72.8, category: 'Amenity', desc: 'Student Hub & Stores', area: 'South Central' }, { name: 'Alan Turing Block', x: 77.0, y: 53.0, category: 'Lab / Academic', desc: 'Computer Science & AI Labs', area: 'East Wing' },
{ name: 'Security Booth', x: 45.9, y: 88.5, category: 'Amenity', desc: 'Main Gate Security Checkpoint', area: 'South Entrance' }, { name: 'Arcade', x: 35.0, y: 57.0, category: 'Amenity', desc: 'Student Hub & Stores', area: 'Central South' },
{ name: 'Parking Area', x: 73.0, y: 86.0, category: 'Parking Area', desc: 'Two-Wheeler & Four-Wheeler Parking', area: 'South East' }, { name: 'Green Building', x: 23.0, y: 71.0, category: 'Admin / Facility', desc: 'Eco-Friendly Academic Complex & Library', area: 'South West' },
{ name: 'Main Entrance', x: 51.8, y: 92.5, category: 'Amenity', desc: 'Primary Campus Security Gate', area: 'South Entrance' }, { name: 'Security Booth', x: 49.0, y: 77.0, category: 'Amenity', desc: 'Main Gate Security Checkpoint', area: 'South Entrance' },
{ name: 'ATM', x: 49.0, y: 86.0, category: 'Amenity', desc: '24/7 Campus Banking ATM', area: 'South Entrance' },
{ name: 'Bike Parking', x: 75.0, y: 67.0, category: 'Parking Area', desc: 'Two-Wheeler Vehicle Parking', area: 'South East' },
{ name: 'Generator Room', x: 76.0, y: 81.0, category: 'Admin / Facility', desc: 'Campus Power & Utility Station', area: 'South East' },
{ name: 'Side Gate', x: 23.0, y: 93.0, category: 'Amenity', desc: 'Secondary Campus Gate', area: 'South West' },
{ name: 'Main Gate', x: 59.0, y: 93.0, category: 'Amenity', desc: 'Primary Campus Entrance', area: 'South Entrance' },
]; ];
const CATEGORY_MAP_SPOTS: Record<string, string[]> = { const CATEGORY_MAP_SPOTS: Record<string, string[]> = {
dept: ['A Block', 'B Block', 'C Block'], dept: ['Homi Jehangir Bhabha Block', 'Nikola Tesla Block', 'C Block', 'Steve Jobs Block', 'Alan Turing Block', 'Green Building'],
library: ['RSB Block'], library: ['Green Building'],
labs: ['Steve Jobs Block', 'A Block', 'B Block', 'C Block', 'EPL Lab'], labs: ['MEC Lab', 'EPL Lab', 'Steve Jobs Block', 'Alan Turing Block', 'Nikola Tesla Block', 'C Block'],
hostel: ['Girls Hostel'], hostel: ['Girls Hostel', 'Boys Mess'],
canteen: ['Canteen', 'Staff Mess', 'Girls Mess', 'Boys Mess'], canteen: ['Canteen', 'Girls Mess', 'Boys Mess', 'Staff Mess', 'Arcade'],
auditorium: ['Steve Jobs Block', 'C Block', 'RSB Block'], auditorium: ['Steve Jobs Block', 'Campus Centre'],
sports: ['Playground'], sports: ['Playground'],
}; };

View File

@@ -1,6 +1,6 @@
import { useState, useMemo } from 'react'; import { useState, useMemo } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Search, Users, Filter, ArrowUpDown, BookOpen, ChevronRight } from 'lucide-react'; import { Search, Users, Filter, ArrowUpDown, BookOpen, ChevronRight, AlertTriangle } from 'lucide-react';
import FacultyCard from '@/components/FacultyCard/FacultyCard'; import FacultyCard from '@/components/FacultyCard/FacultyCard';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer'; import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
import { FACULTY_DATA, DEPARTMENTS } from '@/constants'; import { FACULTY_DATA, DEPARTMENTS } from '@/constants';
@@ -64,6 +64,27 @@ export default function Faculty() {
</div> </div>
<div className="container-custom py-8 space-y-8"> <div className="container-custom py-8 space-y-8">
{/* Under Development Banner */}
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="bg-amber-50 border border-amber-200 rounded-2xl p-4 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 text-amber-900 shadow-2xs"
>
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-amber-500/10 border border-amber-500/20 flex items-center justify-center shrink-0">
<AlertTriangle className="w-5 h-5 text-amber-600" />
</div>
<div>
<div className="text-xs font-bold uppercase tracking-wider text-amber-700" style={{ fontFamily: 'Inter, sans-serif' }}>Under Development</div>
<div className="text-xs font-medium text-amber-900" style={{ fontFamily: 'Inter, sans-serif' }}>
Currently displaying sample mock faculty profiles. Official RIT faculty directory integration is in progress.
</div>
</div>
</div>
<span className="px-3 py-1 rounded-full bg-amber-100 border border-amber-300 text-[11px] font-bold text-amber-800 shrink-0">
Mock Data Active
</span>
</motion.div>
{/* Faculty Hero Section */} {/* Faculty Hero Section */}
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 items-stretch"> <div className="grid grid-cols-1 lg:grid-cols-12 gap-6 items-stretch">
<div className="lg:col-span-5 bg-white rounded-3xl p-8 border border-[#E8ECF4] shadow-sm flex flex-col justify-center relative overflow-hidden"> <div className="lg:col-span-5 bg-white rounded-3xl p-8 border border-[#E8ECF4] shadow-sm flex flex-col justify-center relative overflow-hidden">

View File

@@ -1,73 +1,39 @@
import { motion, useScroll, useTransform } from 'framer-motion'; import { motion, useScroll, useTransform } from 'framer-motion';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { import {
ArrowRight, Sparkles, BookOpen, Bot, GraduationCap, ArrowRight, BookOpen, Bot, GraduationCap,
CheckCircle, Zap, Shield, Brain, Trophy, Award, Cpu, Users CheckCircle, Zap, Shield, Trophy, Award, Cpu, Users, MapPin, Sparkles
} from 'lucide-react'; } from 'lucide-react';
import SectionTitle from '@/components/SectionTitle/SectionTitle'; import SectionTitle from '@/components/SectionTitle/SectionTitle';
import FeatureCard from '@/components/FeatureCard/FeatureCard'; import FeatureCard from '@/components/FeatureCard/FeatureCard';
import FloatingCard from '@/components/FloatingCard/FloatingCard';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer'; import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer'; import AnimatedContainer from '@/components/AnimatedContainer/AnimatedContainer';
import { FEATURES, CAMPUS_LOCATIONS } from '@/constants'; import { FEATURES, CAMPUS_LOCATIONS } from '@/constants';
import * as LucideIcons from 'lucide-react'; import * as LucideIcons from 'lucide-react';
import LazyVideoHero from '@/components/LazyVideoHero/LazyVideoHero';
export default function Home() { export default function Home() {
const { scrollY } = useScroll(); const { scrollY } = useScroll();
const heroOpacity = useTransform(scrollY, [0, 450], [1, 0]); const heroOpacity = useTransform(scrollY, [0, 450], [1, 0]);
const heroY = useTransform(scrollY, [0, 450], [0, -80]); const heroY = useTransform(scrollY, [0, 450], [0, -60]);
return ( return (
<div className="relative"> <div className="relative bg-[#FAFAFA] min-h-screen overflow-hidden">
{/* Sticky Background Video */} {/* ─── Hero Section with IntersectionObserver Lazy Video ─────────────────── */}
<div className="fixed inset-0 w-full h-full z-0 pointer-events-none"> <LazyVideoHero>
<video <section className="relative pt-20 pb-24 z-10">
autoPlay
muted
loop
playsInline
preload="auto"
className="w-full h-full object-cover"
style={{
filter: 'contrast(1.12) brightness(0.88) saturate(1.08)',
transform: 'translate3d(0, 0, 0)',
willChange: 'transform'
}}
>
<source src="/video/RIT Video.webm" type="video/webm" />
<source src="/video/RIT Video.mp4" type="video/mp4" />
</video>
{/* Dark Overlay (40-50%) */}
<div
className="absolute inset-0"
style={{ background: 'linear-gradient(to bottom, rgba(15, 23, 42, 0.4), rgba(15, 23, 42, 0.65))' }}
/>
</div>
{/* ─── Hero Section ─────────────────────────────────────────────────────── */}
<section className="relative overflow-hidden min-h-screen flex flex-col justify-center pt-24 pb-32 z-10 bg-transparent">
{/* Center Content */}
<motion.div <motion.div
className="container-custom relative z-20 flex flex-col items-center justify-center text-center px-4" className="container-custom relative z-20 flex flex-col items-center text-center px-4"
style={{ opacity: heroOpacity, y: heroY }} style={{ opacity: heroOpacity, y: heroY }}
> >
<motion.div {/* Heading */}
initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="mb-3"
>
<span className="text-[#F97316] text-xs font-bold uppercase tracking-widest" style={{ fontFamily: 'Poppins, sans-serif' }}>
OUR LEGACY & FUTURE
</span>
</motion.div>
<motion.h1 <motion.h1
initial={{ opacity: 0, y: 15 }} initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.1 }} transition={{ duration: 0.6, delay: 0.1 }}
className="text-white text-3xl md:text-4xl font-light mb-2" className="text-3xl md:text-5xl font-medium tracking-wide mb-2"
style={{ fontFamily: 'Playfair Display, serif', color: '#FFFFFF', textShadow: '0 2px 10px rgba(0, 0, 0, 0.5)' }} style={{ color: '#FFFFFF', fontFamily: 'Playfair Display, serif', textShadow: '0 2px 14px rgba(0,0,0,0.95)' }}
> >
Welcome to Welcome to
</motion.h1> </motion.h1>
@@ -76,8 +42,8 @@ export default function Home() {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.2 }} transition={{ duration: 0.7, delay: 0.2 }}
className="text-5xl md:text-7xl font-extrabold leading-tight mb-6 bg-gradient-to-r from-[#F97316] via-[#FB923C] to-[#F97316] bg-clip-text text-transparent" className="text-5xl md:text-7xl font-black leading-tight mb-6"
style={{ fontFamily: 'Playfair Display, serif', color: 'transparent', WebkitTextFillColor: 'transparent', filter: 'drop-shadow(0 4px 12px rgba(249, 115, 22, 0.45))' }} style={{ color: '#FFFFFF', fontFamily: 'Playfair Display, serif', textShadow: '0 4px 24px rgba(0,0,0,0.95)' }}
> >
RIT Freshers Hub RIT Freshers Hub
</motion.h2> </motion.h2>
@@ -86,24 +52,24 @@ export default function Home() {
initial={{ opacity: 0, y: 15 }} initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.3 }} transition={{ duration: 0.7, delay: 0.3 }}
className="max-w-2xl text-slate-200 text-sm md:text-base leading-relaxed mb-8" className="max-w-2xl text-slate-100 text-base md:text-lg font-medium leading-relaxed mb-8 drop-shadow-md"
style={{ fontFamily: 'Inter, sans-serif' }} style={{ fontFamily: 'Inter, sans-serif' }}
> >
The RIT Freshers Hub is your centralized gateway to campus life, academic resources, AI assistance, clubs, events, bus routes, notes, and student services. Your centralized gateway to campus life at Rajalakshmi Institute of Technology academic notes, AI assistant, bus routes, faculty directory, and campus navigation.
</motion.p> </motion.p>
{/* Buttons */} {/* Call to Actions */}
<motion.div <motion.div
initial={{ opacity: 0, y: 15 }} initial={{ opacity: 0, y: 15 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.7, delay: 0.4 }} transition={{ duration: 0.7, delay: 0.4 }}
className="flex flex-wrap justify-center gap-4 mb-8" className="flex flex-wrap justify-center gap-4"
> >
<a href="#built-for-freshers"> <a href="#built-for-freshers">
<motion.button <motion.button
whileHover={{ scale: 1.05, boxShadow: '0 8px 25px rgba(249,115,22,0.4)' }} whileHover={{ scale: 1.04, boxShadow: '0 10px 30px -5px rgba(249,115,22,0.4)' }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.96 }}
className="flex items-center gap-2 px-8 py-3.5 rounded-xl text-white font-semibold text-sm cursor-pointer" className="flex items-center gap-2 px-8 py-3.5 rounded-xl text-white font-semibold text-sm cursor-pointer shadow-md"
style={{ style={{
fontFamily: 'Poppins, sans-serif', fontFamily: 'Poppins, sans-serif',
background: 'linear-gradient(135deg, #F97316, #FB923C)', background: 'linear-gradient(135deg, #F97316, #FB923C)',
@@ -111,91 +77,55 @@ export default function Home() {
> >
<BookOpen className="w-4 h-4" /> <BookOpen className="w-4 h-4" />
Explore Hub Explore Hub
<ArrowRight className="w-3.5 h-3.5" /> <ArrowRight className="w-4 h-4" />
</motion.button> </motion.button>
</a> </a>
<Link to="/ai-assistant"> <Link to="/ai-assistant">
<motion.button <motion.button
whileHover={{ scale: 1.05, borderColor: '#F97316', backgroundColor: 'rgba(255,255,255,0.15)' }} whileHover={{ scale: 1.04, borderColor: '#F97316', backgroundColor: 'rgba(255,255,255,0.2)' }}
whileTap={{ scale: 0.95 }} whileTap={{ scale: 0.96 }}
className="flex items-center gap-2 px-8 py-3.5 rounded-xl font-semibold text-sm border-2 border-white/20 text-white bg-white/10 backdrop-blur-md transition-all cursor-pointer" className="flex items-center gap-2 px-8 py-3.5 rounded-xl font-semibold text-sm border-2 border-white/40 text-white bg-black/30 backdrop-blur-md transition-all cursor-pointer shadow-2xs"
style={{ fontFamily: 'Poppins, sans-serif' }} style={{ fontFamily: 'Poppins, sans-serif' }}
> >
<Bot className="w-4 h-4" /> <Bot className="w-4 h-4 text-[#F97316]" />
Ask Chatbot Ask Assistant
</motion.button> </motion.button>
</Link> </Link>
</motion.div> </motion.div>
{/* Badges */}
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.8, delay: 0.5 }}
className="flex flex-wrap justify-center gap-6 text-slate-300 border-t border-white/10 pt-6 max-w-lg w-full"
>
{[
{ icon: CheckCircle, text: 'Official Portal' },
{ icon: Zap, text: 'Instant AI Support' },
{ icon: Shield, text: 'Student Verified' },
].map((badge, i) => (
<div key={i} className="flex items-center gap-1.5 text-xs font-medium" style={{ fontFamily: 'Inter, sans-serif' }}>
<badge.icon className="w-4 h-4 text-[#F97316]" />
<span>{badge.text}</span>
</div>
))}
</motion.div>
</motion.div> </motion.div>
</section> </section>
</LazyVideoHero>
{/* Floating Glassmorphic Stats Section */} {/* Sleek Stats Card Section */}
<section className="relative z-30 -mt-16 px-4"> <section className="relative z-30 -mt-6 px-4">
<div className="container-custom max-w-5xl"> <div className="container-custom max-w-5xl">
<style>{`
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-8px); }
}
.animate-float {
animation: float 6s ease-in-out infinite;
}
`}</style>
<motion.div <motion.div
initial={{ opacity: 0, y: 30 }} initial={{ opacity: 0, y: 30 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, delay: 0.6 }} transition={{ duration: 0.8, delay: 0.6 }}
className="animate-float rounded-[24px] border border-white/10 p-6 md:p-8 shadow-[0_25px_60px_-15px_rgba(0,0,0,0.4)]" className="rounded-3xl border border-[#E5E7EB] p-6 md:p-8 bg-white shadow-[0_20px_50px_-15px_rgba(0,0,0,0.06)]"
style={{
background: 'linear-gradient(135deg, rgba(11, 19, 43, 0.9) 0%, rgba(15, 23, 42, 0.8) 100%)',
backdropFilter: 'blur(20px)',
WebkitBackdropFilter: 'blur(20px)',
}}
> >
<div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-6 text-center"> <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-5 gap-6 text-center">
{[ {[
{ value: '20+', label: 'SPORTS', icon: Trophy }, { value: '20+', label: 'SPORTS', icon: Trophy },
{ value: '100+', label: 'FACULTY', icon: GraduationCap }, { value: '100+', label: 'FACULTY', icon: GraduationCap },
{ value: '18', label: 'CLUBS', icon: Award }, { value: '18', label: 'CLUBS', icon: Award },
{ value: '24/7', label: 'CHATBOT', icon: Cpu }, { value: '24/7', label: 'ASSISTANT', icon: Cpu },
{ value: '5000+', label: 'STUDENTS', icon: Users }, { value: '5000+', label: 'STUDENTS', icon: Users },
].map((stat, i) => ( ].map((stat, i) => (
<div key={i} className="flex flex-col items-center justify-center p-4"> <div key={i} className="flex flex-col items-center justify-center p-3">
{/* Icon */} <div className="w-10 h-10 rounded-xl bg-[#FFF7ED] flex items-center justify-center mb-2.5">
<div className="w-10 h-10 rounded-xl bg-[#F97316]/10 flex items-center justify-center mb-3">
<stat.icon className="w-5 h-5 text-[#F97316]" /> <stat.icon className="w-5 h-5 text-[#F97316]" />
</div> </div>
{/* Value */}
<div <div
className="text-2xl md:text-3xl font-extrabold text-white mb-1" className="text-2xl md:text-3xl font-black text-[#1E293B] mb-0.5"
style={{ fontFamily: 'Poppins, sans-serif' }} style={{ fontFamily: 'Poppins, sans-serif' }}
> >
{stat.value} {stat.value}
</div> </div>
{/* Label */}
<div <div
className="text-[10px] md:text-xs font-semibold tracking-wider text-slate-300 uppercase whitespace-nowrap" className="text-[11px] font-bold tracking-wider text-[#94A3B8] uppercase"
style={{ fontFamily: 'Inter, sans-serif' }} style={{ fontFamily: 'Inter, sans-serif' }}
> >
{stat.label} {stat.label}
@@ -208,7 +138,7 @@ export default function Home() {
</section> </section>
{/* ─── Features Section ─────────────────────────────────────────────────── */} {/* ─── Features Section ─────────────────────────────────────────────────── */}
<section id="built-for-freshers" className="section-padding relative z-20" style={{ backgroundColor: '#FAFAFA' }}> <section id="built-for-freshers" className="section-padding relative z-20">
<div className="container-custom"> <div className="container-custom">
<SectionTitle <SectionTitle
tag="Everything You Need" tag="Everything You Need"
@@ -217,7 +147,7 @@ export default function Home() {
subtitle="From notes to AI assistance — we've got everything you need to navigate campus life with confidence." subtitle="From notes to AI assistance — we've got everything you need to navigate campus life with confidence."
/> />
<StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-5"> <StaggerContainer className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{FEATURES.map((feature) => ( {FEATURES.map((feature) => (
<StaggerItem key={feature.id}> <StaggerItem key={feature.id}>
<FeatureCard feature={feature} /> <FeatureCard feature={feature} />
@@ -227,61 +157,47 @@ export default function Home() {
</div> </div>
</section> </section>
{/* ─── Campus Quick Nav ─────────────────────────────────────────────────── */} {/* ─── Campus Quick Nav ─────────────────────────────────────────────────── */}
<section className="section-padding relative z-20" style={{ backgroundColor: '#FAFAFA' }}> <section className="section-padding relative z-20 bg-white border-y border-[#E5E7EB]">
<div className="container-custom"> <div className="container-custom">
<SectionTitle <SectionTitle
tag="Navigate Campus" tag="Navigate Campus"
title="Explore" title="Explore"
highlight="RIT Campus" highlight="RIT Campus"
subtitle="Find your way around campus — departments, labs, library, and all key locations." subtitle="Find your way around campus — departments, labs, library, and key locations."
/> />
<AnimatedContainer> <AnimatedContainer>
<div className="bg-white rounded-3xl border border-[#E5E7EB] overflow-hidden" style={{ boxShadow: '0 8px 40px -8px rgba(0,0,0,0.1)' }}> <div className="bg-white rounded-3xl border border-[#E5E7EB] overflow-hidden shadow-sm">
{/* Map placeholder */} {/* Map Teaser Header */}
<div <div
className="h-56 flex items-center justify-center relative overflow-hidden" className="h-52 flex items-center justify-center relative overflow-hidden"
style={{ background: 'linear-gradient(135deg, #1E293B, #334155)' }} style={{ background: 'linear-gradient(135deg, #1E293B, #0F172A)' }}
> >
<div <div
className="absolute inset-0 opacity-10" className="absolute inset-0 opacity-15"
style={{ style={{
backgroundImage: 'linear-gradient(rgba(249,115,22,0.5) 1px, transparent 1px), linear-gradient(90deg, rgba(249,115,22,0.5) 1px, transparent 1px)', backgroundImage: 'linear-gradient(rgba(249,115,22,0.4) 1px, transparent 1px), linear-gradient(90deg, rgba(249,115,22,0.4) 1px, transparent 1px)',
backgroundSize: '30px 30px', backgroundSize: '28px 28px',
}} }}
/> />
{/* Location pins */}
{[ <div className="relative z-10 text-center px-4">
{ label: 'CSE Block', x: '25%', y: '30%' }, <div className="w-12 h-12 rounded-2xl bg-[#F97316]/20 border border-[#F97316]/30 flex items-center justify-center mx-auto mb-3">
{ label: 'Library', x: '55%', y: '45%' }, <MapPin className="w-6 h-6 text-[#F97316]" />
{ label: 'Canteen', x: '70%', y: '65%' },
{ label: 'Hostel', x: '20%', y: '65%' },
].map((pin, i) => (
<motion.div
key={i}
className="absolute flex flex-col items-center gap-1 cursor-pointer group"
style={{ left: pin.x, top: pin.y, transform: 'translate(-50%, -50%)' }}
whileHover={{ scale: 1.1 }}
>
<div className="w-6 h-6 rounded-full flex items-center justify-center shadow-lg group-hover:scale-110 transition-all"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)' }}>
<div className="w-2 h-2 rounded-full bg-white" />
</div> </div>
<span className="text-[10px] text-white font-medium bg-black/50 px-1.5 py-0.5 rounded-full opacity-0 group-hover:opacity-100 transition-opacity whitespace-nowrap"> <h3 className="text-white text-lg font-bold mb-1" style={{ fontFamily: 'Poppins, sans-serif' }}>
{pin.label} Interactive Campus Map
</span> </h3>
</motion.div> <p className="text-slate-400 text-xs mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>
))} Locate all major campus blocks, labs, library & amenities.
<div className="relative z-10 text-center"> </p>
<div className="text-white/40 text-sm" style={{ fontFamily: 'Inter, sans-serif' }}>Interactive Campus Map</div>
<Link to="/campus"> <Link to="/campus">
<motion.button <motion.button
whileHover={{ scale: 1.05 }} whileHover={{ scale: 1.05 }}
className="mt-2 px-5 py-2 rounded-xl text-white text-xs font-semibold" whileTap={{ scale: 0.95 }}
style={{ background: 'rgba(249,115,22,0.8)', fontFamily: 'Poppins, sans-serif' }} className="px-5 py-2 rounded-xl text-white text-xs font-semibold shadow-sm cursor-pointer"
style={{ background: 'linear-gradient(135deg, #F97316, #FB923C)', fontFamily: 'Poppins, sans-serif' }}
> >
Open Full Map Open Full Map
</motion.button> </motion.button>
@@ -289,20 +205,20 @@ export default function Home() {
</div> </div>
</div> </div>
{/* Quick nav buttons */} {/* Quick Nav Grid */}
<div className="p-5 grid grid-cols-3 sm:grid-cols-7 gap-3"> <div className="p-5 grid grid-cols-3 sm:grid-cols-7 gap-3 bg-[#FAFAFA]">
{CAMPUS_LOCATIONS.map((loc) => { {CAMPUS_LOCATIONS.map((loc) => {
const Icon = (LucideIcons as unknown as Record<string, React.ComponentType<{ className?: string }>>)[loc.icon]; const Icon = (LucideIcons as unknown as Record<string, React.ComponentType<{ className?: string }>>)[loc.icon];
return ( return (
<Link to="/campus" key={loc.id}> <Link to="/campus" key={loc.id}>
<motion.div <motion.div
whileHover={{ y: -3, backgroundColor: '#FFF7ED' }} whileHover={{ y: -3, backgroundColor: '#FFF7ED' }}
className="flex flex-col items-center gap-2 p-3 rounded-xl cursor-pointer transition-all border border-transparent hover:border-[#FED7AA]" className="flex flex-col items-center gap-2 p-3 rounded-xl cursor-pointer transition-all border border-transparent hover:border-[#FED7AA] bg-white shadow-2xs"
> >
<div className="w-9 h-9 rounded-xl bg-[#F8FAFC] flex items-center justify-center"> <div className="w-9 h-9 rounded-xl bg-[#FFF7ED] flex items-center justify-center">
{Icon && <Icon className="w-4.5 h-4.5 text-[#F97316]" />} {Icon && <Icon className="w-4.5 h-4.5 text-[#F97316]" />}
</div> </div>
<span className="text-[10px] text-[#475569] text-center leading-tight" style={{ fontFamily: 'Inter, sans-serif' }}> <span className="text-[11px] text-[#475569] text-center font-medium leading-tight" style={{ fontFamily: 'Inter, sans-serif' }}>
{loc.name} {loc.name}
</span> </span>
</motion.div> </motion.div>
@@ -314,44 +230,6 @@ export default function Home() {
</AnimatedContainer> </AnimatedContainer>
</div> </div>
</section> </section>
{/* ─── Community Teaser ─────────────────────────────────────────────────── */}
<section className="section-padding bg-white relative z-20">
<div className="container-custom max-w-2xl">
{/* Q&A teaser */}
<AnimatedContainer>
<div className="rounded-3xl p-7 border border-[#E5E7EB]"
style={{ background: 'linear-gradient(135deg, #FAFAFA, #FFF7ED)', boxShadow: '0 4px 20px -4px rgba(0,0,0,0.07)' }}>
<div className="flex items-center gap-3 mb-4">
<div className="w-11 h-11 rounded-xl flex items-center justify-center bg-[#FFF7ED]">
<LucideIcons.MessageCircle className="w-5 h-5 text-[#F97316]" />
</div>
<div>
<h3 className="font-semibold text-[#1E293B]" style={{ fontFamily: 'Poppins, sans-serif' }}>Freshers Q&A</h3>
<p className="text-xs text-[#94A3B8]">Get answers from seniors</p>
</div>
</div>
<div className="space-y-3 mb-5">
{['How is hostel life at RIT?', 'Best clubs to join as a fresher?', 'Tips for first semester survival'].map((q, i) => (
<div key={i} className="flex items-start gap-2.5 p-3 bg-white rounded-xl border border-[#E5E7EB]">
<span className="text-[#F97316] text-xs font-bold mt-0.5">Q</span>
<span className="text-xs text-[#475569]" style={{ fontFamily: 'Inter, sans-serif' }}>{q}</span>
</div>
))}
</div>
<Link to="/community">
<motion.button
whileHover={{ scale: 1.02 }}
className="w-full py-2.5 rounded-xl text-sm font-semibold text-[#F97316] border-2 border-[#F97316] hover:bg-[#FFF7ED] transition-all"
style={{ fontFamily: 'Poppins, sans-serif' }}
>
Join the Discussion
</motion.button>
</Link>
</div>
</AnimatedContainer>
</div>
</section>
</div> </div>
); );
} }

View File

@@ -1,7 +1,7 @@
import { useState, useRef, useEffect } from 'react'; import { useState, useRef, useEffect } from 'react';
import { useSearchParams } from 'react-router-dom'; import { useSearchParams } from 'react-router-dom';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { Search, Download, Filter, BookOpen, FileText, ScrollText, BookMarked, ChevronRight, ArrowLeft, ExternalLink, Code, Globe, GitBranch, Cloud, Palette, Shield, DollarSign, BarChart3, Users, Megaphone, TrendingUp, UserCheck, ClipboardList, ShoppingCart, Cpu, Activity, LayoutGrid, Layers, Zap, Settings, Gauge, Radio, Wifi, Bot, Sun, Wrench, Binary, BatteryCharging, CircuitBoard, ShieldCheck, BrainCircuit, FlaskConical, Lock, Terminal, Dna, Microscope, Sparkles, Smartphone, Monitor, Server, CheckCircle2, Database, Briefcase } from 'lucide-react'; import { Search, Download, Filter, BookOpen, FileText, ScrollText, BookMarked, ChevronRight, ArrowLeft, ExternalLink, Code, Globe, GitBranch, Cloud, Palette, Shield, DollarSign, BarChart3, Users, Megaphone, TrendingUp, UserCheck, ClipboardList, ShoppingCart, Cpu, Activity, LayoutGrid, Layers, Zap, Settings, Gauge, Radio, Wifi, Bot, Sun, Wrench, Binary, BatteryCharging, CircuitBoard, ShieldCheck, BrainCircuit, FlaskConical, Lock, Terminal, Dna, Microscope, Sparkles, Smartphone, Monitor, Server, CheckCircle2, Database, Briefcase, AlertTriangle } from 'lucide-react';
import SectionTitle from '@/components/SectionTitle/SectionTitle'; import SectionTitle from '@/components/SectionTitle/SectionTitle';
import { getBackendUrl } from '@/lib/utils'; import { getBackendUrl } from '@/lib/utils';
import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer'; import { StaggerContainer, StaggerItem } from '@/components/AnimatedContainer/AnimatedContainer';
@@ -1432,6 +1432,28 @@ export default function Notes() {
)} )}
<div className="container-custom py-10"> <div className="container-custom py-10">
{/* Under Development Banner */}
<motion.div
initial={{ opacity: 0, y: -10 }}
animate={{ opacity: 1, y: 0 }}
className="bg-amber-50 border border-amber-200 rounded-2xl p-4 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3 text-amber-900 shadow-2xs mb-8"
>
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-amber-500/10 border border-amber-500/20 flex items-center justify-center shrink-0">
<AlertTriangle className="w-5 h-5 text-amber-600" />
</div>
<div>
<div className="text-xs font-bold uppercase tracking-wider text-amber-700" style={{ fontFamily: 'Inter, sans-serif' }}>Under Development</div>
<div className="text-xs font-medium text-amber-900" style={{ fontFamily: 'Inter, sans-serif' }}>
Currently displaying sample mock notes & syllabus data. Official semester notes repository integration is in progress.
</div>
</div>
</div>
<span className="px-3 py-1 rounded-full bg-amber-100 border border-amber-300 text-[11px] font-bold text-amber-800 shrink-0">
Mock Data Active
</span>
</motion.div>
{selectedToolkit ? ( {selectedToolkit ? (
(() => { (() => {
const TOOLKIT_MAP: Record<string, { categories: typeof CS_TOOLS_CATEGORIES; label: string }> = { const TOOLKIT_MAP: Record<string, { categories: typeof CS_TOOLS_CATEGORIES; label: string }> = {

View File

@@ -579,6 +579,7 @@ def telegram_polling_thread():
if text == "/start": if text == "/start":
welcome_text = ( welcome_text = (
f"👋 *Welcome to the RIT Chatbot 24/7!*\n\n" f"👋 *Welcome to the RIT Chatbot 24/7!*\n\n"
f"⚠️ *Please note: Answers generated by AI may occasionally be inaccurate or wrong. Always verify critical academic or administrative details with official campus sources.*\n\n"
f"I can help you answer any questions about RIT Chennai — courses, hostels, transport, sports, and more.\n\n" f"I can help you answer any questions about RIT Chennai — courses, hostels, transport, sports, and more.\n\n"
f"🚀 *Developer Collaboration:* Type `/collab` to post your project idea!\n" f"🚀 *Developer Collaboration:* Type `/collab` to post your project idea!\n"
f"🗑️ *Remove Request:* Type `/remove` to cancel your active requests.\n\n" f"🗑️ *Remove Request:* Type `/remove` to cancel your active requests.\n\n"
@@ -811,31 +812,9 @@ async def on_message(message):
mention_nick_str = f"<@!{discord_client.user.id}>" mention_nick_str = f"<@!{discord_client.user.id}>"
content = content.replace(mention_str, "").replace(mention_nick_str, "").strip() content = content.replace(mention_str, "").replace(mention_nick_str, "").strip()
# Discord /remove Command # Discord /collab & /remove commands disabled as requested
if content.lower().startswith("/remove") or content.lower().startswith("/cancel"): if content.lower().startswith("/collab") or content.lower().startswith("/remove") or content.lower().startswith("/cancel"):
try: await message.reply(" **Dev Hub collaboration requests via Discord are currently disabled.** Please use the RIT Dev Hub website (https://rit-services.in/dev-collab) or the Telegram bot (@Ritchatbot_bot).")
res = requests.get(f"{BACKEND_URL}/api/collab/active/discord/{message.author.id}", timeout=5)
if res.status_code == 200:
requests_list = res.json()
if not requests_list:
await message.reply(" **You have no active collaboration requests to remove.**")
else:
view = DiscordCollabRemoveView(requests_list)
await message.reply("🗑️ **Your Active Collaboration Requests**\nSelect a request from the dropdown below to cancel it:", view=view)
else:
await message.reply("❌ Failed to fetch active requests.")
except Exception as e:
await message.reply(f"❌ Error: {e}")
return
# Discord Interactive /collab command
if content.lower().startswith("/collab"):
view = CollabView()
await message.reply(
"🚀 **Post a Collaboration Request to RIT Dev Hub!**\n"
"Please select a tag from the scroll-down dropdown menu below to open the submission form:",
view=view
)
return return
# Check if this message is a reply to a question DM sent to a helper # Check if this message is a reply to a question DM sent to a helper
@@ -1003,12 +982,8 @@ def send_collab_application(payload: CollabApplicationPayload):
telegram_sent = False telegram_sent = False
discord_sent = False discord_sent = False
# 1. Telegram Broadcast via 24/7 Chatbot & Collab Bot # 1. Telegram Notification strictly to Person A (the project author)
chat_id = payload.telegram_chat_id chat_id = payload.telegram_chat_id
if not chat_id:
helpers = current_config.get("helper_chat_ids", [])
if helpers:
chat_id = helpers[0]
if chat_id: if chat_id:
msg_text = ( msg_text = (
@@ -1031,36 +1006,11 @@ def send_collab_application(payload: CollabApplicationPayload):
res = send_telegram_message(chat_id, msg_text, reply_markup=reply_markup, token=bot_token) res = send_telegram_message(chat_id, msg_text, reply_markup=reply_markup, token=bot_token)
if res.get("ok"): if res.get("ok"):
telegram_sent = True telegram_sent = True
else:
logging.info(f"Skipping Telegram notification for collab application {payload.application_id}: No telegram_chat_id provided for author.")
# 2. Discord Broadcast # 2. Discord Broadcast - Disabled as requested
discord_target_users = [] discord_sent = False
if payload.discord_user_id:
discord_target_users.append(payload.discord_user_id)
config_discord_helpers = current_config.get("discord_helper_user_ids", [])
for dh in config_discord_helpers:
if str(dh) not in [str(x) for x in discord_target_users]:
discord_target_users.append(dh)
if discord_target_users and DISCORD_TOKEN and discord_loop:
try:
asyncio.run_coroutine_threadsafe(
broadcast_discord_collab_application(
payload.application_id,
payload.project_idea,
payload.tag,
payload.applicant_name,
payload.applicant_dept,
payload.applicant_year,
payload.applicant_contact,
payload.message or "",
discord_target_users
),
discord_loop
)
discord_sent = True
except Exception as e:
logging.error(f"Error scheduling Discord collab broadcast: {e}")
return { return {
"status": "success", "status": "success",