Compare commits
28 Commits
01a3df2386
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1cbee2f4c8 | ||
|
|
d81b2682b4 | ||
|
|
e5725615be | ||
|
|
3319868eca | ||
|
|
14b87b9d12 | ||
|
|
4ef114fdcd | ||
|
|
20a0dd842f | ||
|
|
3397bb5ed1 | ||
|
|
08974b9cd8 | ||
|
|
c1fcc83840 | ||
|
|
e8886203ed | ||
|
|
04c4ed907c | ||
|
|
d76e956b07 | ||
|
|
f198b43e4c | ||
|
|
c101cf05ed | ||
|
|
fca13b2e2c | ||
|
|
0ac0646614 | ||
|
|
c7891cd992 | ||
|
|
8433fe33b2 | ||
|
|
2a86af0113 | ||
|
|
1852239452 | ||
|
|
b461c6cf32 | ||
|
|
68902caaa4 | ||
|
|
6229128dd2 | ||
|
|
7067f7cbad | ||
|
|
674eb437b4 | ||
|
|
5676bb0950 | ||
|
|
add0fbb47c |
42
.gitea/workflows/deploy.yml
Normal file
42
.gitea/workflows/deploy.yml
Normal 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
42
.github/workflows/deploy.yml
vendored
Normal 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!"
|
||||||
@@ -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
84
CICD_GUIDE.md
Normal 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
62
GITEA_CICD_GUIDE.md
Normal 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
223
VPS_MASTER_GUIDE.md
Normal 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.
|
||||||
BIN
WhatsApp Video 2026-08-04 at 08.43.10.mp4
Normal file
BIN
WhatsApp Video 2026-08-04 at 08.43.10.mp4
Normal file
Binary file not shown.
@@ -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
public/map.png
BIN
public/map.png
Binary file not shown.
|
Before Width: | Height: | Size: 1.8 MiB After Width: | Height: | Size: 282 KiB |
Binary file not shown.
BIN
public/video/whatsapp-video.mp4
Normal file
BIN
public/video/whatsapp-video.mp4
Normal file
Binary file not shown.
@@ -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'
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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";
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
<resources>
|
|
||||||
<string name="app_name">RIT Driver Tracker</string>
|
|
||||||
</resources>
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
# Project-wide Gradle settings.
|
|
||||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
|
||||||
android.useAndroidX=true
|
|
||||||
android.enableJetifier=true
|
|
||||||
BIN
rit-driver-app/gradle/wrapper/gradle-wrapper.jar
vendored
BIN
rit-driver-app/gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
@@ -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
248
rit-driver-app/gradlew
vendored
@@ -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" "$@"
|
|
||||||
93
rit-driver-app/gradlew.bat
vendored
93
rit-driver-app/gradlew.bat
vendored
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -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
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
BIN
src/assets/map.png.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 282 KiB |
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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' }}>
|
||||||
|
|||||||
84
src/components/LazyVideoHero/LazyVideoHero.tsx
Normal file
84
src/components/LazyVideoHero/LazyVideoHero.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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
|
<motion.div
|
||||||
muted
|
className="container-custom relative z-20 flex flex-col items-center text-center px-4"
|
||||||
loop
|
style={{ opacity: heroOpacity, y: heroY }}
|
||||||
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
|
|
||||||
className="container-custom relative z-20 flex flex-col items-center justify-center text-center px-4"
|
|
||||||
style={{ opacity: heroOpacity, y: heroY }}
|
|
||||||
>
|
|
||||||
<motion.div
|
|
||||||
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' }}>
|
{/* Heading */}
|
||||||
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%' },
|
</div>
|
||||||
{ label: 'Hostel', x: '20%', y: '65%' },
|
<h3 className="text-white text-lg font-bold mb-1" style={{ fontFamily: 'Poppins, sans-serif' }}>
|
||||||
].map((pin, i) => (
|
Interactive Campus Map
|
||||||
<motion.div
|
</h3>
|
||||||
key={i}
|
<p className="text-slate-400 text-xs mb-3" style={{ fontFamily: 'Inter, sans-serif' }}>
|
||||||
className="absolute flex flex-col items-center gap-1 cursor-pointer group"
|
Locate all major campus blocks, labs, library & amenities.
|
||||||
style={{ left: pin.x, top: pin.y, transform: 'translate(-50%, -50%)' }}
|
</p>
|
||||||
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>
|
|
||||||
<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">
|
|
||||||
{pin.label}
|
|
||||||
</span>
|
|
||||||
</motion.div>
|
|
||||||
))}
|
|
||||||
<div className="relative z-10 text-center">
|
|
||||||
<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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }> = {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user