diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..9f62ec0 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,5 @@ +node_modules +# Keep environment variables out of version control +.env + +/generated/prisma diff --git a/backend/config/db.js b/backend/config/db.js index dfd50b9..811bb3d 100644 --- a/backend/config/db.js +++ b/backend/config/db.js @@ -1,69 +1,10 @@ -// const { Pool } = require('pg'); -import { Pool } from 'pg'; -import dotenv from 'dotenv'; -dotenv.config(); +import "dotenv/config"; +import { PrismaPg } from '@prisma/adapter-pg' +import { PrismaClient } from '../generated/prisma/client.js'; -const pool = new Pool({ - user: process.env.DB_USER, - host: process.env.DB_HOST, - database: process.env.DB_NAME, - password: process.env.DB_PASSWORD, - port: process.env.DB_PORT, -}); +const connectionString = `${process.env.DATABASE_URL}` -const initDB = async () => { - const client = await pool.connect(); - - try { - await client.query(` - CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - email VARCHAR(255) UNIQUE NOT NULL, - password VARCHAR(255) NOT NULL, - role VARCHAR(50) DEFAULT 'user', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - `); - await client.query(` - CREATE TABLE IF NOT EXISTS events ( - id SERIAL PRIMARY KEY, - title VARCHAR(255) NOT NULL, - description TEXT, - date DATE, - max_team_size INTEGER DEFAULT 1, - registration_fee DECIMAL(10, 2), - qr_code_image VARCHAR(255), - status VARCHAR(50) DEFAULT 'active', - created_by INTEGER REFERENCES users(id), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - `); +const adapter = new PrismaPg({ connectionString }) +const prisma = new PrismaClient({ adapter }) - await client.query(` - CREATE TABLE IF NOT EXISTS registrations ( - id SERIAL PRIMARY KEY, - event_id INTEGER REFERENCES events(id) ON DELETE CASCADE, - user_id INTEGER REFERENCES users(id), - team_size INTEGER NOT NULL, - team_leader_name VARCHAR(255) NOT NULL, - team_leader_email VARCHAR(255) NOT NULL, - team_leader_contact VARCHAR(20) NOT NULL, - team_members JSONB, - payment_screenshot VARCHAR(255), - transaction_id VARCHAR(255), - status VARCHAR(50) DEFAULT 'pending', - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - `); - - console.log('Database tables created successfully'); - } catch (error) { - console.error('Database initialization error:', error); - throw error; - } finally { - client.release(); - } -}; - -// module.exports = { pool, initDB }; -export { pool, initDB }; \ No newline at end of file +export { prisma } \ No newline at end of file diff --git a/backend/controllers/adminController.js b/backend/controllers/adminController.js index dd98ad9..11a9a36 100644 --- a/backend/controllers/adminController.js +++ b/backend/controllers/adminController.js @@ -1,24 +1,27 @@ -import { pool } from '../config/db.js'; +import { prisma } from '../config/db.js'; import bcrypt from 'bcryptjs'; const getDashboardStats = async (req, res) => { try { - const usersCount = await pool.query('SELECT COUNT(*) FROM users WHERE role = $1', ['user']); - const eventsCount = await pool.query('SELECT COUNT(*) FROM events'); - const registrationsCount = await pool.query('SELECT COUNT(*) FROM registrations'); - const pendingCount = await pool.query( - 'SELECT COUNT(*) FROM registrations WHERE status = $1', - ['pending'] - ); - - res.json({ - stats: { - totalUsers: parseInt(usersCount.rows[0].count), - totalEvents: parseInt(eventsCount.rows[0].count), - totalRegistrations: parseInt(registrationsCount.rows[0].count), - pendingRegistrations: parseInt(pendingCount.rows[0].count) - } - }); + const [usersCount, eventsCount, registrationsCount, pendingCount] = await Promise.all([ + prisma.user.count({ + where: { role: 'user' } + }), + prisma.event.count(), + prisma.registration.count(), + prisma.registration.count({ + where: { status: 'pending' } + }) +]); + +res.json({ + stats: { + totalUsers: usersCount, + totalEvents: eventsCount, + totalRegistrations: registrationsCount, + pendingRegistrations: pendingCount + } +}); } catch (error) { console.error('Get dashboard stats error:', error); res.status(500).json({ message: 'Server error' }); @@ -26,11 +29,19 @@ const getDashboardStats = async (req, res) => { }; const getAllUsers = async (req, res) => { try { - const result = await pool.query( - 'SELECT id, email, role, created_at FROM users ORDER BY created_at DESC' - ); + const result = await prisma.user.findMany({ + select: { + id: true, + email: true, + role: true, + createdAt: true + }, + orderBy: { + createdAt: 'desc' + } + }); - res.json({ users: result.rows }); + res.json({ users: result }); } catch (error) { console.error('Get all users error:', error); res.status(500).json({ message: 'Server error' }); @@ -42,27 +53,26 @@ const updateUser = async (req, res) => { const { id } = req.params; const { email, role, password } = req.body; - let query = 'UPDATE users SET email = $1, role = $2'; - let params = [email, role]; - + const updateData = { + email, + role, +}; if (password) { - const hashedPassword = await bcrypt.hash(password, 10); - query += ', password = $3'; - params.push(hashedPassword); - } - - query += ` WHERE id = $${params.length + 1} RETURNING id, email, role, created_at`; - params.push(id); - - const result = await pool.query(query, params); - - if (result.rows.length === 0) { - return res.status(404).json({ message: 'User not found' }); - } - + updateData.password = await bcrypt.hash(password, 10); +} +const updatedUser = await prisma.user.update({ + where: { id: parseInt(id) }, + data: updateData, + select: { + id: true, + email: true, + role: true, + createdAt: true, + }, + }); res.json({ message: 'User updated successfully', - user: result.rows[0] + user: updatedUser }); } catch (error) { console.error('Update user error:', error); @@ -74,14 +84,18 @@ const deleteUser = async (req, res) => { try { const { id } = req.params; - const user = await pool.query('SELECT role FROM users WHERE id = $1', [id]); - if (user.rows.length > 0 && user.rows[0].role === 'admin') { + const user = await prisma.user.findUnique({ + where: { id: parseInt(id) }, + select: { role: true } + }); + if (user && user.role === 'admin') { return res.status(403).json({ message: 'Cannot delete admin account' }); } - const result = await pool.query('DELETE FROM users WHERE id = $1 RETURNING *', [id]); - - if (result.rows.length === 0) { + const result = await prisma.user.delete({ + where: { id: parseInt(id) } + }); + if (!result) { return res.status(404).json({ message: 'User not found' }); } @@ -96,25 +110,20 @@ const getAllRegistrations = async (req, res) => { try { const { eventId } = req.query; - let query = ` - SELECT r.*, e.title as event_title, e.date as event_date, - u.email as user_email - FROM registrations r - JOIN events e ON r.event_id = e.id - JOIN users u ON r.user_id = u.id - `; - - const params = []; - if (eventId) { - query += ' WHERE r.event_id = $1'; - params.push(eventId); - } - - query += ' ORDER BY r.created_at DESC'; - - const result = await pool.query(query, params); + const registrations = await prisma.registration.findMany({ + where: eventId ? { eventId: Number(eventId) } : {}, + orderBy: { createdAt: 'desc' }, + include: { + event: { + select: { title: true, date: true } + }, + user: { + select: { email: true } + } + } + }); - res.json({ registrations: result.rows }); + res.json({ registrations: registrations }); } catch (error) { console.error('Get all registrations error:', error); res.status(500).json({ message: 'Server error' }); @@ -130,18 +139,18 @@ const updateRegistrationStatus = async (req, res) => { return res.status(400).json({ message: 'Invalid status' }); } - const result = await pool.query( - 'UPDATE registrations SET status = $1 WHERE id = $2 RETURNING *', - [status, id] - ); + const updatedRegistration = await prisma.registration.update({ + where: { id: parseInt(id) }, + data: { status: status } + }); - if (result.rows.length === 0) { + if (!updatedRegistration) { return res.status(404).json({ message: 'Registration not found' }); } res.json({ message: 'Registration status updated successfully', - registration: result.rows[0] + registration: updatedRegistration }); } catch (error) { console.error('Update registration status error:', error); @@ -162,22 +171,26 @@ const updateRegistration = async (req, res) => { status } = req.body; - const result = await pool.query( - `UPDATE registrations - SET team_size = $1, team_leader_name = $2, team_leader_email = $3, - team_leader_contact = $4, team_members = $5, transaction_id = $6, status = $7 - WHERE id = $8 RETURNING *`, - [teamSize, teamLeaderName, teamLeaderEmail, teamLeaderContact, - teamMembers, transactionId, status, id] - ); + const updatedRegistration = await prisma.registration.update({ + where: { id: parseInt(id) }, + data: { + teamSize, + teamLeaderName, + teamLeaderEmail, + teamLeaderContact, + teamMembers, + transactionId, + status + } + }); - if (result.rows.length === 0) { + if (!updatedRegistration) { return res.status(404).json({ message: 'Registration not found' }); } res.json({ message: 'Registration updated successfully', - registration: result.rows[0] + registration: updatedRegistration }); } catch (error) { console.error('Update registration error:', error); @@ -189,9 +202,10 @@ const deleteRegistration = async (req, res) => { try { const { id } = req.params; - const result = await pool.query('DELETE FROM registrations WHERE id = $1 RETURNING *', [id]); - - if (result.rows.length === 0) { + const deletedRegistration = await prisma.registration.delete({ + where: { id: parseInt(id) } + }); + if (!deletedRegistration) { return res.status(404).json({ message: 'Registration not found' }); } diff --git a/backend/controllers/authController.js b/backend/controllers/authController.js index e208e3e..4a62d6c 100644 --- a/backend/controllers/authController.js +++ b/backend/controllers/authController.js @@ -1,6 +1,6 @@ import bcrypt from 'bcryptjs'; import jwt from 'jsonwebtoken'; -import { pool } from '../config/db.js'; +import { prisma } from '../config/db.js'; const register = async (req, res) => { try { @@ -18,23 +18,23 @@ const register = async (req, res) => { return res.status(400).json({ message: 'Password must be at least 6 characters long' }); } - const userExists = await pool.query( - 'SELECT * FROM users WHERE email = $1', - [email] - ); + const UserExist=await prisma.user.findUnique({ + where:{email:email} + }); - if (userExists.rows.length > 0) { + if (UserExist) { return res.status(400).json({ message: 'User already exists with this email' }); } const hashedPassword = await bcrypt.hash(password, 10); - const result = await pool.query( - 'INSERT INTO users (email, password, role) VALUES ($1, $2, $3) RETURNING id, email, role, created_at', - [email, hashedPassword, 'user'] - ); - - const user = result.rows[0]; + const user = await prisma.user.create({ + data: { + email, + password: hashedPassword, + role: 'user' + } + }); const token = jwt.sign( { id: user.id, email: user.email, role: user.role }, @@ -66,15 +66,13 @@ const login = async (req, res) => { return res.status(400).json({ message: 'Email and password are required' }); } - const result = await pool.query( - 'SELECT * FROM users WHERE email = $1', - [email] - ); + const user = await prisma.user.findUnique({ + where: { email: email } + }); - if (result.rows.length === 0) { + if (!user) { return res.status(401).json({ message: 'Invalid email or password' }); } - const user = result.rows[0]; const isValidPassword = await bcrypt.compare(password, user.password); if (!isValidPassword) { return res.status(401).json({ message: 'Invalid email or password' }); @@ -102,16 +100,15 @@ const login = async (req, res) => { const getCurrentUser = async (req, res) => { try { - const result = await pool.query( - 'SELECT id, email, role, created_at FROM users WHERE id = $1', - [req.user.id] - ); + const user = await prisma.user.findUnique({ + where: { id: req.user.id } + }); - if (result.rows.length === 0) { + if (!user) { return res.status(404).json({ message: 'User not found' }); } - res.json({ user: result.rows[0] }); + res.json({ user: user }); } catch (error) { console.error('Get current user error:', error); res.status(500).json({ message: 'Server error' }); diff --git a/backend/controllers/eventController.js b/backend/controllers/eventController.js index fe297a0..2d02641 100644 --- a/backend/controllers/eventController.js +++ b/backend/controllers/eventController.js @@ -1,15 +1,17 @@ -import { pool } from '../config/db.js'; +import { prisma } from "../config/db.js"; const getAllEvents = async (req, res) => { try { - const result = await pool.query(` - SELECT e.*, u.email as created_by_email - FROM events e - LEFT JOIN users u ON e.created_by = u.id - WHERE e.status = 'active' - ORDER BY e.created_at DESC - `); - - res.json({ events: result.rows }); + const events = await prisma.event.findMany({ + where: { status: 'active' }, + orderBy: { createdAt: 'desc' }, + include: { + createdBy: { + select: { email: true } + } + } + }); + + res.json({ events: events }); } catch (error) { console.error('Get events error:', error); res.status(500).json({ message: 'Failed to fetch events' }); @@ -19,18 +21,20 @@ const getEventById = async (req, res) => { try { const { id } = req.params; - const result = await pool.query(` - SELECT e.*, u.email as created_by_email - FROM events e - LEFT JOIN users u ON e.created_by = u.id - WHERE e.id = $1 - `, [id]); + const event = await prisma.event.findUnique({ + where: { id: Number(id) }, + include: { + createdBy: { + select: { email: true } + } + } + }); - if (result.rows.length === 0) { + if (!event) { return res.status(404).json({ message: 'Event not found' }); } - res.json({ event: result.rows[0] }); + res.json({ event: event }); } catch (error) { console.error('Get event error:', error); res.status(500).json({ message: 'Failed to fetch event' }); @@ -47,15 +51,22 @@ const createEvent = async (req, res) => { return res.status(400).json({ message: 'Title, description, and date are required' }); } - const result = await pool.query( - `INSERT INTO events (title, description, date, max_team_size, registration_fee, qr_code_image, created_by) - VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING *`, - [title, description, date, maxTeamSize || 1, registrationFee || 0, qrCodeImage, req.user.id] + const event = await prisma.event.create({ + data: { + title, + description, + date:date ? new Date(date) : null, + max_team_size : maxTeamSize || 1, + registration_fee: registrationFee || 0, + qr_code_image: qrCodeImage, + createdBy: { connect: { id: req.user.id } } + } + } ); res.status(201).json({ message: 'Event created successfully', - event: result.rows[0] + event: event }); } catch (error) { console.error('Create event error:', error); @@ -68,21 +79,26 @@ const updateEvent = async (req, res) => { const { title, description, date, maxTeamSize, registrationFee, status } = req.body; const qrCodeImage = req.file ? req.file.path : req.body.existingQrCode; - const result = await pool.query( - `UPDATE events - SET title = $1, description = $2, date = $3, max_team_size = $4, - registration_fee = $5, qr_code_image = $6, status = $7 - WHERE id = $8 RETURNING *`, - [title, description, date, maxTeamSize, registrationFee, qrCodeImage, status || 'active', id] - ); + const result = await prisma.event.update({ + where: { id: Number(id) }, + data: { + title, + description, + date, + maxTeamSize, + registrationFee, + qrCodeImage, + status: status || 'active' + } + }) - if (result.rows.length === 0) { + if (!result) { return res.status(404).json({ message: 'Event not found' }); } res.json({ message: 'Event updated successfully', - event: result.rows[0] + event: result }); } catch (error) { console.error('Update event error:', error); @@ -93,9 +109,11 @@ const deleteEvent = async (req, res) => { try { const { id } = req.params; - const result = await pool.query('DELETE FROM events WHERE id = $1 RETURNING *', [id]); + const result = await prisma.event.delete({ + where: { id: Number(id) } + }); - if (result.rows.length === 0) { + if (!result) { return res.status(404).json({ message: 'Event not found' }); } diff --git a/backend/controllers/registrationController.js b/backend/controllers/registrationController.js index 778dd8f..c196517 100644 --- a/backend/controllers/registrationController.js +++ b/backend/controllers/registrationController.js @@ -1,4 +1,4 @@ -import { pool } from '../config/db.js'; +import { prisma } from "../config/db.js"; const createRegistration = async (req, res) => { try { const { @@ -34,13 +34,15 @@ const createRegistration = async (req, res) => { } } } - const eventCheck = await pool.query('SELECT * FROM events WHERE id = $1', [eventId]); - if (eventCheck.rows.length === 0) { + const eventCheck = await prisma.event.findUnique({ + where: { id: Number(eventId) } + }); + if (!eventCheck) { return res.status(404).json({ message: 'Event not found' }); } - const event = eventCheck.rows[0]; - if (event.registration_fee > 0) { + const event = eventCheck; + if (event.registrationFee > 0) { if (!paymentScreenshot) { return res.status(400).json({ message: 'Payment screenshot is required for this event' }); } @@ -48,36 +50,35 @@ const createRegistration = async (req, res) => { return res.status(400).json({ message: 'Transaction ID is required' }); } } - const existingReg = await pool.query( - 'SELECT * FROM registrations WHERE event_id = $1 AND user_id = $2', - [eventId, req.user.id] - ); + const existingReg = await prisma.registration.findFirst({ + where: { + event_id: Number(eventId), + user_id: req.user.id + } + }); - if (existingReg.rows.length > 0) { + if (existingReg) { return res.status(400).json({ message: 'You have already registered for this event' }); } - const result = await pool.query( - `INSERT INTO registrations - (event_id, user_id, team_size, team_leader_name, team_leader_email, - team_leader_contact, team_members, payment_screenshot, transaction_id, status) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING *`, - [ - eventId, - req.user.id, - teamSize, - teamLeaderName, - teamLeaderEmail, - teamLeaderContact, - teamMembers || null, - paymentScreenshot, - transactionId || null, - 'pending' - ] - ); + const result = await prisma.registration.create({ + data: { + event_id: Number(eventId), + user_id: req.user.id, + team_size: teamSize, + team_leader_name: teamLeaderName, + team_leader_email: teamLeaderEmail, + team_leader_contact: teamLeaderContact, + team_members: teamMembers || null, + payment_screenshot: paymentScreenshot, + transaction_id: transactionId || null, + status: 'pending' + } + }); + res.status(201).json({ message: 'Registration submitted successfully! Await admin approval.', - registration: result.rows[0] + registration: result }); } catch (error) { console.error('Create registration error:', error); @@ -86,16 +87,17 @@ const createRegistration = async (req, res) => { }; const getUserRegistrations = async (req, res) => { try { - const result = await pool.query( - `SELECT r.*, e.title as event_title, e.date as event_date - FROM registrations r - JOIN events e ON r.event_id = e.id - WHERE r.user_id = $1 - ORDER BY r.created_at DESC`, - [req.user.id] - ); + const result = await prisma.registration.findMany({ + where: { user_id: req.user.id }, + orderBy: { createdAt: 'desc' }, + include: { + event: { + select: { title: true, date: true } + } + } + }); - res.json({ registrations: result.rows }); + res.json({ registrations: result }); } catch (error) { console.error('Get user registrations error:', error); res.status(500).json({ message: 'Failed to fetch registrations' }); @@ -104,24 +106,26 @@ const getUserRegistrations = async (req, res) => { const getRegistrationById = async (req, res) => { try { const { id } = req.params; - - const result = await pool.query( - `SELECT r.*, e.title as event_title, e.date as event_date, u.email as user_email - FROM registrations r - JOIN events e ON r.event_id = e.id - JOIN users u ON r.user_id = u.id - WHERE r.id = $1`, - [id] - ); + const result = await prisma.registration.findUnique({ + where: { id: Number(id) }, + include: { + event: { + select: { title: true, date: true } + }, + user: { + select: { email: true } + } + } + }); - if (result.rows.length === 0) { + if (!result) { return res.status(404).json({ message: 'Registration not found' }); } - if (result.rows[0].user_id !== req.user.id && req.user.role !== 'admin') { + if (result.user_id !== req.user.id && req.user.role !== 'admin') { return res.status(403).json({ message: 'Access denied' }); } - res.json({ registration: result.rows[0] }); + res.json({ registration: result }); } catch (error) { console.error('Get registration error:', error); res.status(500).json({ message: 'Failed to fetch registration' }); diff --git a/backend/middleware/rateLimiter.js b/backend/middleware/rateLimiter.js new file mode 100644 index 0000000..e69de29 diff --git a/backend/package-lock.json b/backend/package-lock.json index 932e1cf..76a11ab 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -18,10 +18,10 @@ "jsonwebtoken": "^9.0.3", "multer": "^1.4.5-lts.1", "multer-storage-cloudinary": "^4.0.0", - "pg": "^8.17.2" + "pg": "^8.18.0" }, "devDependencies": { - "@types/node": "^25.0.10", + "@types/node": "^25.1.0", "@types/pg": "^8.16.0", "nodemon": "^3.0.1", "prisma": "^7.3.0" @@ -336,9 +336,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "25.0.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.10.tgz", - "integrity": "sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==", + "version": "25.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz", + "integrity": "sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==", "dev": true, "license": "MIT", "dependencies": { @@ -1969,12 +1969,12 @@ "license": "MIT" }, "node_modules/pg": { - "version": "8.17.2", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.17.2.tgz", - "integrity": "sha512-vjbKdiBJRqzcYw1fNU5KuHyYvdJ1qpcQg1CeBrHFqV1pWgHeVR6j/+kX0E1AAXfyuLUGY1ICrN2ELKA/z2HWzw==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.18.0.tgz", + "integrity": "sha512-xqrUDL1b9MbkydY/s+VZ6v+xiMUmOUk7SS9d/1kpyQxoJ6U9AO1oIJyUWVZojbfe5Cc/oluutcgFG4L9RDP1iQ==", "license": "MIT", "dependencies": { - "pg-connection-string": "^2.10.1", + "pg-connection-string": "^2.11.0", "pg-pool": "^3.11.0", "pg-protocol": "^1.11.0", "pg-types": "2.2.0", @@ -2003,9 +2003,9 @@ "optional": true }, "node_modules/pg-connection-string": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.10.1.tgz", - "integrity": "sha512-iNzslsoeSH2/gmDDKiyMqF64DATUCWj3YJ0wP14kqcsf2TUklwimd+66yYojKwZCA7h2yRNLGug71hCBA2a4sw==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.11.0.tgz", + "integrity": "sha512-kecgoJwhOpxYU21rZjULrmrBJ698U2RxXofKVzOn5UDj61BPj/qMb7diYUR1nLScCDbrztQFl1TaQZT0t1EtzQ==", "license": "MIT" }, "node_modules/pg-int8": { diff --git a/backend/package.json b/backend/package.json index fe876e4..c38d3fa 100644 --- a/backend/package.json +++ b/backend/package.json @@ -19,10 +19,10 @@ "jsonwebtoken": "^9.0.3", "multer": "^1.4.5-lts.1", "multer-storage-cloudinary": "^4.0.0", - "pg": "^8.17.2" + "pg": "^8.18.0" }, "devDependencies": { - "@types/node": "^25.0.10", + "@types/node": "^25.1.0", "@types/pg": "^8.16.0", "nodemon": "^3.0.1", "prisma": "^7.3.0" diff --git a/backend/prisma.config.ts b/backend/prisma.config.ts new file mode 100644 index 0000000..831a20f --- /dev/null +++ b/backend/prisma.config.ts @@ -0,0 +1,14 @@ +// This file was generated by Prisma, and assumes you have installed the following: +// npm install --save-dev prisma dotenv +import "dotenv/config"; +import { defineConfig } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations", + }, + datasource: { + url: process.env["DATABASE_URL"], + }, +}); diff --git a/backend/prisma/migrations/20260131175617_init/migration.sql b/backend/prisma/migrations/20260131175617_init/migration.sql new file mode 100644 index 0000000..40257ff --- /dev/null +++ b/backend/prisma/migrations/20260131175617_init/migration.sql @@ -0,0 +1,61 @@ +-- CreateEnum +CREATE TYPE "Role" AS ENUM ('user', 'admin'); + +-- CreateTable +CREATE TABLE "User" ( + "id" SERIAL NOT NULL, + "email" TEXT NOT NULL, + "password" TEXT NOT NULL, + "role" "Role" NOT NULL DEFAULT 'user', + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "User_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Event" ( + "id" SERIAL NOT NULL, + "title" TEXT NOT NULL, + "description" TEXT, + "date" DATE, + "max_team_size" INTEGER NOT NULL DEFAULT 1, + "registration_fee" DECIMAL(65,30), + "qr_code_image" TEXT, + "status" TEXT NOT NULL DEFAULT 'active', + "createdAt" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(6) NOT NULL, + "createdById" INTEGER, + + CONSTRAINT "Event_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Registration" ( + "id" SERIAL NOT NULL, + "event_id" INTEGER NOT NULL, + "user_id" INTEGER, + "team_size" INTEGER NOT NULL, + "team_leader_name" VARCHAR(255) NOT NULL, + "team_leader_email" VARCHAR(255) NOT NULL, + "team_leader_contact" VARCHAR(20) NOT NULL, + "team_members" JSONB, + "payment_screenshot" VARCHAR(255), + "transaction_id" VARCHAR(255), + "status" VARCHAR(50) NOT NULL DEFAULT 'pending', + "createdAt" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "Registration_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "User_email_key" ON "User"("email"); + +-- AddForeignKey +ALTER TABLE "Event" ADD CONSTRAINT "Event_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Registration" ADD CONSTRAINT "Registration_event_id_fkey" FOREIGN KEY ("event_id") REFERENCES "Event"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Registration" ADD CONSTRAINT "Registration_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/backend/prisma/migrations/20260131183656_init/migration.sql b/backend/prisma/migrations/20260131183656_init/migration.sql new file mode 100644 index 0000000..5d44858 --- /dev/null +++ b/backend/prisma/migrations/20260131183656_init/migration.sql @@ -0,0 +1,4 @@ +-- AlterTable +ALTER TABLE "Event" ALTER COLUMN "max_team_size" SET DEFAULT '1', +ALTER COLUMN "max_team_size" SET DATA TYPE TEXT, +ALTER COLUMN "registration_fee" SET DATA TYPE TEXT; diff --git a/backend/prisma/migrations/20260131184207_init/migration.sql b/backend/prisma/migrations/20260131184207_init/migration.sql new file mode 100644 index 0000000..ae31b9c --- /dev/null +++ b/backend/prisma/migrations/20260131184207_init/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "Registration" ALTER COLUMN "team_size" SET DATA TYPE TEXT; diff --git a/backend/prisma/migrations/migration_lock.toml b/backend/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/backend/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma new file mode 100644 index 0000000..b0c0e98 --- /dev/null +++ b/backend/prisma/schema.prisma @@ -0,0 +1,69 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" + output = "../generated/prisma" +} + +datasource db { + provider = "postgresql" +} + + + + +model User{ + id Int @id @default(autoincrement()) + email String @unique + password String + role Role @default(user) + events Event[] + registrations Registration[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + + +enum Role { + user + admin +} + +model Event{ + id Int @id @default(autoincrement()) + title String + description String? + date DateTime? @db.Date + max_team_size String @default("1") + registration_fee String? + qr_code_image String? + status String @default("active") + registrations Registration[] + createdAt DateTime @default(now()) @db.Timestamp(6) + updatedAt DateTime @updatedAt @db.Timestamp(6) + createdById Int? + createdBy User? @relation(fields: [createdById], references: [id]) +} + + +model Registration { + id Int @id @default(autoincrement()) + event_id Int + user_id Int? + team_size String + team_leader_name String @db.VarChar(255) + team_leader_email String @db.VarChar(255) + team_leader_contact String @db.VarChar(20) + team_members Json? @db.JsonB + payment_screenshot String? @db.VarChar(255) + transaction_id String? @db.VarChar(255) + status String @default("pending") @db.VarChar(50) + createdAt DateTime @default(now()) @db.Timestamp(6) + event Event @relation(fields: [event_id], references: [id], onDelete: Cascade) + user User? @relation(fields: [user_id], references: [id]) + +} \ No newline at end of file diff --git a/backend/server.js b/backend/server.js index 92356f5..bfc083b 100644 --- a/backend/server.js +++ b/backend/server.js @@ -7,11 +7,12 @@ import dotenv from 'dotenv'; dotenv.config(); -import { pool, initDB } from './config/db.js'; + import authRoutes from './routes/authRoutes.js'; import eventRoutes from './routes/eventRoutes.js'; import registrationRoutes from './routes/registrationRoutes.js'; import adminRoutes from './routes/adminRoutes.js'; +import { prisma } from './config/db.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -44,21 +45,22 @@ app.use((err, req, res, next) => { const initializeApp = async () => { try { - await initDB(); - const adminExists = await pool.query( - 'SELECT * FROM users WHERE email = $1', - [process.env.ADMIN_EMAIL || 'abesec.codechef@gmail.com'] - ); + const adminExists = await prisma.user.findUnique({ + where: { email: process.env.ADMIN_EMAIL || 'abesec.codechef@gmail.com' } + }); - if (adminExists.rows.length === 0) { + if (!adminExists) { const hashedPassword = await bcrypt.hash(process.env.ADMIN_PASSWORD, 10); - await pool.query( - 'INSERT INTO users (email, password, role) VALUES ($1, $2, $3)', - [process.env.ADMIN_EMAIL || 'abesec.codechef@gmail.com', hashedPassword, 'admin'] - ); + await prisma.user.create({ + data: { + email: process.env.ADMIN_EMAIL || 'abesec.codechef@gmail.com', + password: hashedPassword, + role: 'admin' + } + }); console.log(' Default admin user created'); - console.log(' Email:', process.env.ADMIN_EMAIL); + console.log(' Email:', process.env.ADMIN_EMAIL || 'abesec.codechef@gmail.com'); console.log(' Password:', process.env.ADMIN_PASSWORD); } app.listen(PORT, () => { diff --git a/package-lock.json b/package-lock.json index 78455e5..69e6e94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,10 +14,10 @@ "@studio-freight/lenis": "^1.0.42", "@tabler/icons-react": "^3.35.0", "@tailwindcss/vite": "^4.1.17", - "axios": "^1.13.3", + "axios": "^1.13.4", "clsx": "^2.1.1", "face-api.js": "^0.22.2", - "framer-motion": "^12.23.24", + "framer-motion": "^12.31.0", "gsap": "^3.13.0", "lenis": "^1.3.14", "lucide-react": "^0.553.0", @@ -26,6 +26,7 @@ "postprocessing": "^6.38.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-hot-toast": "^2.6.0", "react-icons": "^5.5.0", "react-router-dom": "^7.9.5", "react-scroll": "^1.9.3", @@ -40,6 +41,7 @@ "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.4", + "baseline-browser-mapping": "^2.9.19", "eslint": "^9.36.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.22", @@ -2030,9 +2032,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.3.tgz", - "integrity": "sha512-ERT8kdX7DZjtUm7IitEyV7InTHAF42iJuMArIiDIV5YtPanJkgw4hw5Dyg9fh0mihdWNn1GKaeIWErfe56UQ1g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", + "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -2068,9 +2070,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.8.23", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.23.tgz", - "integrity": "sha512-616V5YX4bepJFzNyOfce5Fa8fDJMfoxzOIzDCZwaGL8MKVpFrXqfNUoIpRn9YMI5pXf/VKgzjB4htFMsFKKdiQ==", + "version": "2.9.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", + "integrity": "sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2863,13 +2865,13 @@ } }, "node_modules/framer-motion": { - "version": "12.23.24", - "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.23.24.tgz", - "integrity": "sha512-HMi5HRoRCTou+3fb3h9oTLyJGBxHfW+HnNE25tAXOvVx/IvwMHK0cx7IR4a2ZU6sh3IX1Z+4ts32PcYBOqka8w==", + "version": "12.31.0", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.31.0.tgz", + "integrity": "sha512-Tnd0FU05zGRFI3JJmBegXonF1rfuzYeuXd1QSdQ99Ysnppk0yWBWSW2wUsqzRpS5nv0zPNx+y0wtDj4kf0q5RQ==", "license": "MIT", "dependencies": { - "motion-dom": "^12.23.23", - "motion-utils": "^12.23.6", + "motion-dom": "^12.30.1", + "motion-utils": "^12.29.2", "tslib": "^2.4.0" }, "peerDependencies": { @@ -2991,6 +2993,15 @@ "integrity": "sha512-b/ZCF6amfAUb7dJM/MxRs7AetQEahYzJ8PtgfrmEdtw6uyGOr+ZSGtgjFm6mfsBkxJ4d2W7kg+Nlqzqvn3Bc0w==", "license": "MIT" }, + "node_modules/goober": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.18.tgz", + "integrity": "sha512-2vFqsaDVIT9Gz7N6kAL++pLpp41l3PfDuusHcjnGLfR6+huZkl6ziX+zgVC3ZxpqWhzH6pyDdGrCeDhMIvwaxw==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -3742,18 +3753,18 @@ } }, "node_modules/motion-dom": { - "version": "12.23.23", - "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.23.23.tgz", - "integrity": "sha512-n5yolOs0TQQBRUFImrRfs/+6X4p3Q4n1dUEqt/H58Vx7OW6RF+foWEgmTVDhIWJIMXOuNNL0apKH2S16en9eiA==", + "version": "12.30.1", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.30.1.tgz", + "integrity": "sha512-QXB+iFJRzZTqL+Am4a1CRoHdH+0Nq12wLdqQQZZsfHlp9AMt6PA098L/61oVZsDA+Ep3QSGudzpViyRrhYhGcQ==", "license": "MIT", "dependencies": { - "motion-utils": "^12.23.6" + "motion-utils": "^12.29.2" } }, "node_modules/motion-utils": { - "version": "12.23.6", - "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.23.6.tgz", - "integrity": "sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==", + "version": "12.29.2", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.29.2.tgz", + "integrity": "sha512-G3kc34H2cX2gI63RqU+cZq+zWRRPSsNIOjpdl9TN4AQwC4sgwYPl/Q/Obf/d53nOm569T0fYK+tcoSV50BWx8A==", "license": "MIT" }, "node_modules/ms": { @@ -4043,6 +4054,23 @@ "react": "^19.2.0" } }, + "node_modules/react-hot-toast": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.6.0.tgz", + "integrity": "sha512-bH+2EBMZ4sdyou/DPrfgIouFpcRLCJ+HoCA32UoAYHn6T3Ur5yfcDCeSr5mwldl6pFOsiocmrXMuoCJ1vV8bWg==", + "license": "MIT", + "dependencies": { + "csstype": "^3.1.3", + "goober": "^2.1.16" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-icons": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-5.5.0.tgz", diff --git a/package.json b/package.json index 58ff3c6..17865f6 100644 --- a/package.json +++ b/package.json @@ -16,10 +16,10 @@ "@studio-freight/lenis": "^1.0.42", "@tabler/icons-react": "^3.35.0", "@tailwindcss/vite": "^4.1.17", - "axios": "^1.13.3", + "axios": "^1.13.4", "clsx": "^2.1.1", "face-api.js": "^0.22.2", - "framer-motion": "^12.23.24", + "framer-motion": "^12.31.0", "gsap": "^3.13.0", "lenis": "^1.3.14", "lucide-react": "^0.553.0", @@ -28,6 +28,7 @@ "postprocessing": "^6.38.0", "react": "^19.1.1", "react-dom": "^19.1.1", + "react-hot-toast": "^2.6.0", "react-icons": "^5.5.0", "react-router-dom": "^7.9.5", "react-scroll": "^1.9.3", @@ -42,6 +43,7 @@ "@types/react": "^19.1.16", "@types/react-dom": "^19.1.9", "@vitejs/plugin-react": "^5.0.4", + "baseline-browser-mapping": "^2.9.19", "eslint": "^9.36.0", "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-react-refresh": "^0.4.22", diff --git a/public/wheel/DialWheel.svg b/public/wheel/DialWheel.svg new file mode 100644 index 0000000..2b78927 --- /dev/null +++ b/public/wheel/DialWheel.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/wheel/hand.svg b/public/wheel/hand.svg new file mode 100644 index 0000000..d5cb125 --- /dev/null +++ b/public/wheel/hand.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/wheel/hand_left.svg b/public/wheel/hand_left.svg new file mode 100644 index 0000000..418c6ed --- /dev/null +++ b/public/wheel/hand_left.svg @@ -0,0 +1,3 @@ + + + diff --git a/public/wheel/hands_line.svg b/public/wheel/hands_line.svg new file mode 100644 index 0000000..a83c03a --- /dev/null +++ b/public/wheel/hands_line.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/public/wheel/rays.svg b/public/wheel/rays.svg new file mode 100644 index 0000000..09e91fe --- /dev/null +++ b/public/wheel/rays.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App.css b/src/App.css index 032f33d..509afa5 100644 --- a/src/App.css +++ b/src/App.css @@ -4,76 +4,24 @@ font-weight: 100 900; font-display: swap; } + @font-face { font-family: "Technor"; src: url("./assets/fonts/Technor-Variable.ttf") format("truetype"); font-weight: 100 900; font-display: swap; } -.offering-container{ - - background: #000000; - overflow-x: hidden; -} -.offering-main{ - padding-top: 1rem; -} -.offering-main h2{ - font-size: 4rem; - text-align: center; -font-family: 'Panchang', sans-serif; - -} -.offering-main p{ - font-size: 1.2rem; - text-align: center; - margin-top: 1rem; -} - -.offering-main span{ -text-transform: uppercase; - font-weight: 800; +@keyframes spin-slow { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } } -.cards-container{ - width: 20rem; - padding-bottom: 2rem; - padding: 1rem; -} -.cards{ - background: transparent !important; - padding: 3rem ; -border: 1px solid rgba(255, 255, 255, 0.2); - border-radius: 1rem; - backdrop-filter: blur(10px); -} -.cards h3{ - font-family: 'Technor',sans-serif; - font-size: 1.2rem; -} -.cards span{ - font-size: 0.8rem; -} -@media (max-width:480px) { - .cards-container{ - padding: 5rem; - gap: 3rem; - } - .cards{ - padding: 2rem; - gap: 1rem; - } - .offering-main{ - padding-top: 5rem; - padding: 1.8rem; - } - .offering-main h2{ - font-size: 2.3rem; - } - .offering-main p{ - font-size: 1rem; - margin-top: 1rem; - } +.animate-spin-slow { + animation: spin-slow 20s linear infinite; } \ No newline at end of file diff --git a/src/components/Achievements.jsx b/src/components/Achievements.jsx index 7005e0c..6cb5907 100644 --- a/src/components/Achievements.jsx +++ b/src/components/Achievements.jsx @@ -153,26 +153,92 @@ const AchievementsSection = () => { ]; return ( -
+
- {/* Responsive Hero Section */} -
-
-

- - Celebrating{' '} - Our - - - Remarkable{' '} - Achievements - -

+ {/* Hero Section */} +
+ {/* Subtle background pattern */} +
+
+
+ +
+ {/* Heading */} +
+

+ + Remarkable{' '} + Achievements + + + Celebrating{' '} + Excellence + +

+
+ + {/* Achievement Cards */} +
+ {/* Card 1 - Trophy */} +
+
+
+ +
+
+

+ Excellence in Innovation +

+

+ Recognizing outstanding contributions to technology and creative problem-solving +

+
+
+
+ + {/* Card 2 - Medal (Hidden on mobile) */} +
+
+
+ + + +
+
+

+ Team Excellence +

+

+ Celebrating collaborative success and collective achievements +

+
+
+
+
-
+
{achievements.map((achievement, index) => (
{ }; return ( -
-
-
-
+
+
+
+
-
+
{achievement.name}
-
- {achievement.badge} +
+ {achievement.badge}
-

+

{achievement.name}

-

+

{achievement.description}

@@ -263,7 +329,7 @@ const AchievementCard = ({ achievement }) => { href={achievement.github} target="_blank" rel="noopener noreferrer" - className="group flex items-center justify-center w-10 h-10 sm:w-12 sm:h-12 bg-gray-900 text-white rounded-lg hover:bg-gray-800 transition-all duration-300 hover:scale-110" + className="group flex items-center justify-center w-10 h-10 sm:w-12 sm:h-12 bg-gray-700 text-white rounded-lg hover:bg-gray-600 transition-all duration-300 hover:scale-110" title="GitHub" > diff --git a/src/components/BackgroundOverlayCard.jsx b/src/components/BackgroundOverlayCard.jsx index a7f7106..9dfa8bc 100644 --- a/src/components/BackgroundOverlayCard.jsx +++ b/src/components/BackgroundOverlayCard.jsx @@ -12,7 +12,7 @@ export const BackgroundOverlayCard = ({
diff --git a/src/components/CircularGallery.jsx b/src/components/CircularGallery.jsx new file mode 100644 index 0000000..6a4b849 --- /dev/null +++ b/src/components/CircularGallery.jsx @@ -0,0 +1,475 @@ +import { Camera, Mesh, Plane, Program, Renderer, Texture, Transform } from 'ogl'; +import { useEffect, useRef } from 'react'; + +function debounce(func, wait) { + let timeout; + return function (...args) { + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, args), wait); + }; +} + +function lerp(p1, p2, t) { + return p1 + (p2 - p1) * t; +} + +function autoBind(instance) { + const proto = Object.getPrototypeOf(instance); + Object.getOwnPropertyNames(proto).forEach(key => { + if (key !== 'constructor' && typeof instance[key] === 'function') { + instance[key] = instance[key].bind(instance); + } + }); +} + +function createTextTexture(gl, text, font = 'bold 30px monospace', color = 'black') { + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + context.font = font; + const metrics = context.measureText(text); + const textWidth = Math.ceil(metrics.width); + const textHeight = Math.ceil(parseInt(font, 10) * 1.2); + canvas.width = textWidth + 20; + canvas.height = textHeight + 20; + context.font = font; + context.fillStyle = color; + context.textBaseline = 'middle'; + context.textAlign = 'center'; + context.clearRect(0, 0, canvas.width, canvas.height); + context.fillText(text, canvas.width / 2, canvas.height / 2); + const texture = new Texture(gl, { generateMipmaps: false }); + texture.image = canvas; + return { texture, width: canvas.width, height: canvas.height }; +} + +class Title { + constructor({ gl, plane, renderer, text, textColor = '#545050', font = '30px sans-serif' }) { + autoBind(this); + this.gl = gl; + this.plane = plane; + this.renderer = renderer; + this.text = text; + this.textColor = textColor; + this.font = font; + this.createMesh(); + } + createMesh() { + const { texture, width, height } = createTextTexture(this.gl, this.text, this.font, this.textColor); + const geometry = new Plane(this.gl); + const program = new Program(this.gl, { + vertex: ` + attribute vec3 position; + attribute vec2 uv; + uniform mat4 modelViewMatrix; + uniform mat4 projectionMatrix; + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } + `, + fragment: ` + precision highp float; + uniform sampler2D tMap; + varying vec2 vUv; + void main() { + vec4 color = texture2D(tMap, vUv); + if (color.a < 0.1) discard; + gl_FragColor = color; + } + `, + uniforms: { tMap: { value: texture } }, + transparent: true + }); + this.mesh = new Mesh(this.gl, { geometry, program }); + const aspect = width / height; + const textHeight = this.plane.scale.y * 0.15; + const textWidth = textHeight * aspect; + this.mesh.scale.set(textWidth, textHeight, 1); + this.mesh.position.y = -this.plane.scale.y * 0.5 - textHeight * 0.5 - 0.05; + this.mesh.setParent(this.plane); + } +} + +class Media { + constructor({ + geometry, + gl, + image, + index, + length, + renderer, + scene, + screen, + text, + viewport, + bend, + textColor, + borderRadius = 0, + font + }) { + this.extra = 0; + this.geometry = geometry; + this.gl = gl; + this.image = image; + this.index = index; + this.length = length; + this.renderer = renderer; + this.scene = scene; + this.screen = screen; + this.text = text; + this.viewport = viewport; + this.bend = bend; + this.textColor = textColor; + this.borderRadius = borderRadius; + this.font = font; + this.createShader(); + this.createMesh(); + this.createTitle(); + this.onResize(); + } + createShader() { + const texture = new Texture(this.gl, { + generateMipmaps: true + }); + this.program = new Program(this.gl, { + depthTest: false, + depthWrite: false, + vertex: ` + precision highp float; + attribute vec3 position; + attribute vec2 uv; + uniform mat4 modelViewMatrix; + uniform mat4 projectionMatrix; + uniform float uTime; + uniform float uSpeed; + varying vec2 vUv; + void main() { + vUv = uv; + vec3 p = position; + p.z = (sin(p.x * 4.0 + uTime) * 1.5 + cos(p.y * 2.0 + uTime) * 1.5) * (0.1 + uSpeed * 0.5); + gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0); + } + `, + fragment: ` + precision highp float; + uniform vec2 uImageSizes; + uniform vec2 uPlaneSizes; + uniform sampler2D tMap; + uniform float uBorderRadius; + varying vec2 vUv; + + float roundedBoxSDF(vec2 p, vec2 b, float r) { + vec2 d = abs(p) - b; + return length(max(d, vec2(0.0))) + min(max(d.x, d.y), 0.0) - r; + } + + void main() { + vec2 ratio = vec2( + min((uPlaneSizes.x / uPlaneSizes.y) / (uImageSizes.x / uImageSizes.y), 1.0), + min((uPlaneSizes.y / uPlaneSizes.x) / (uImageSizes.y / uImageSizes.x), 1.0) + ); + vec2 uv = vec2( + vUv.x * ratio.x + (1.0 - ratio.x) * 0.5, + vUv.y * ratio.y + (1.0 - ratio.y) * 0.5 + ); + vec4 color = texture2D(tMap, uv); + + float d = roundedBoxSDF(vUv - 0.5, vec2(0.5 - uBorderRadius), uBorderRadius); + + // Smooth antialiasing for edges + float edgeSmooth = 0.002; + float alpha = 1.0 - smoothstep(-edgeSmooth, edgeSmooth, d); + + gl_FragColor = vec4(color.rgb, alpha); + } + `, + uniforms: { + tMap: { value: texture }, + uPlaneSizes: { value: [0, 0] }, + uImageSizes: { value: [0, 0] }, + uSpeed: { value: 0 }, + uTime: { value: 100 * Math.random() }, + uBorderRadius: { value: this.borderRadius } + }, + transparent: true + }); + const img = new Image(); + img.crossOrigin = 'anonymous'; + img.src = this.image; + img.onload = () => { + texture.image = img; + this.program.uniforms.uImageSizes.value = [img.naturalWidth, img.naturalHeight]; + }; + } + createMesh() { + this.plane = new Mesh(this.gl, { + geometry: this.geometry, + program: this.program + }); + this.plane.setParent(this.scene); + } + createTitle() { + this.title = new Title({ + gl: this.gl, + plane: this.plane, + renderer: this.renderer, + text: this.text, + textColor: this.textColor, + fontFamily: this.font + }); + } + update(scroll, direction) { + this.plane.position.x = this.x - scroll.current - this.extra; + + const x = this.plane.position.x; + const H = this.viewport.width / 2; + + if (this.bend === 0) { + this.plane.position.y = 0; + this.plane.rotation.z = 0; + } else { + const B_abs = Math.abs(this.bend); + const R = (H * H + B_abs * B_abs) / (2 * B_abs); + const effectiveX = Math.min(Math.abs(x), H); + + const arc = R - Math.sqrt(R * R - effectiveX * effectiveX); + if (this.bend > 0) { + this.plane.position.y = -arc; + this.plane.rotation.z = -Math.sign(x) * Math.asin(effectiveX / R); + } else { + this.plane.position.y = arc; + this.plane.rotation.z = Math.sign(x) * Math.asin(effectiveX / R); + } + } + + this.speed = scroll.current - scroll.last; + this.program.uniforms.uTime.value += 0.04; + this.program.uniforms.uSpeed.value = this.speed; + + const planeOffset = this.plane.scale.x / 2; + const viewportOffset = this.viewport.width / 2; + this.isBefore = this.plane.position.x + planeOffset < -viewportOffset; + this.isAfter = this.plane.position.x - planeOffset > viewportOffset; + if (direction === 'right' && this.isBefore) { + this.extra -= this.widthTotal; + this.isBefore = this.isAfter = false; + } + if (direction === 'left' && this.isAfter) { + this.extra += this.widthTotal; + this.isBefore = this.isAfter = false; + } + } + onResize({ screen, viewport } = {}) { + if (screen) this.screen = screen; + if (viewport) { + this.viewport = viewport; + if (this.plane.program.uniforms.uViewportSizes) { + this.plane.program.uniforms.uViewportSizes.value = [this.viewport.width, this.viewport.height]; + } + } + this.scale = this.screen.height / 1500; + this.plane.scale.y = (this.viewport.height * (900 * this.scale)) / this.screen.height; + this.plane.scale.x = (this.viewport.width * (700 * this.scale)) / this.screen.width; + this.plane.program.uniforms.uPlaneSizes.value = [this.plane.scale.x, this.plane.scale.y]; + this.padding = 2; + this.width = this.plane.scale.x + this.padding; + this.widthTotal = this.width * this.length; + this.x = this.width * this.index; + } +} + +class App { + constructor( + container, + { + items, + bend, + textColor = '#ffffff', + borderRadius = 0, + font = 'bold 30px Figtree', + scrollSpeed = 2, + scrollEase = 0.05 + } = {} + ) { + document.documentElement.classList.remove('no-js'); + this.container = container; + this.scrollSpeed = scrollSpeed; + this.scroll = { ease: scrollEase, current: 0, target: 0, last: 0 }; + this.onCheckDebounce = debounce(this.onCheck, 200); + this.createRenderer(); + this.createCamera(); + this.createScene(); + this.onResize(); + this.createGeometry(); + this.createMedias(items, bend, textColor, borderRadius, font); + this.update(); + this.addEventListeners(); + } + createRenderer() { + this.renderer = new Renderer({ + alpha: true, + antialias: true, + dpr: Math.min(window.devicePixelRatio || 1, 2) + }); + this.gl = this.renderer.gl; + this.gl.clearColor(0, 0, 0, 0); + this.container.appendChild(this.gl.canvas); + } + createCamera() { + this.camera = new Camera(this.gl); + this.camera.fov = 45; + this.camera.position.z = 20; + } + createScene() { + this.scene = new Transform(); + } + createGeometry() { + this.planeGeometry = new Plane(this.gl, { + heightSegments: 50, + widthSegments: 100 + }); + } + createMedias(items, bend = 1, textColor, borderRadius, font) { + const defaultItems = [ + { image: `/bc2.webp?grayscale`, text: '' }, + { image: `/rust4.webp?grayscale`, text: '' }, + { image: `/rust6.webp?grayscale`, text: '' }, + { image: `/hn2.webp?grayscale`, text: '' }, + { image: `/COC1.JPG?grayscale`, text: '' }, + { image: `/TERROR1.webp?grayscale`, text: '' }, + { image: `/OUAC1.webp?grayscale`, text: '' }, + { image: `/OUAC2.webp?grayscale`, text: '' }, + { image: `/COC4.jpeg`, text: '' }, + { image: `/rust3.webp`, text: '' }, + { image: `hn1.webp`, text: '' }, + + ]; + const galleryItems = items && items.length ? items : defaultItems; + this.mediasImages = galleryItems.concat(galleryItems); + this.medias = this.mediasImages.map((data, index) => { + return new Media({ + geometry: this.planeGeometry, + gl: this.gl, + image: data.image, + index, + length: this.mediasImages.length, + renderer: this.renderer, + scene: this.scene, + screen: this.screen, + text: data.text, + viewport: this.viewport, + bend, + textColor, + borderRadius, + font + }); + }); + } + onTouchDown(e) { + this.isDown = true; + this.scroll.position = this.scroll.current; + this.start = e.touches ? e.touches[0].clientX : e.clientX; + } + onTouchMove(e) { + if (!this.isDown) return; + const x = e.touches ? e.touches[0].clientX : e.clientX; + const distance = (this.start - x) * (this.scrollSpeed * 0.025); + this.scroll.target = this.scroll.position + distance; + } + onTouchUp() { + this.isDown = false; + this.onCheck(); + } + onWheel(e) { + const delta = e.deltaY || e.wheelDelta || e.detail; + this.scroll.target += (delta > 0 ? this.scrollSpeed : -this.scrollSpeed) * 0.2; + this.onCheckDebounce(); + } + onCheck() { + if (!this.medias || !this.medias[0]) return; + const width = this.medias[0].width; + const itemIndex = Math.round(Math.abs(this.scroll.target) / width); + const item = width * itemIndex; + this.scroll.target = this.scroll.target < 0 ? -item : item; + } + onResize() { + this.screen = { + width: this.container.clientWidth, + height: this.container.clientHeight + }; + this.renderer.setSize(this.screen.width, this.screen.height); + this.camera.perspective({ + aspect: this.screen.width / this.screen.height + }); + const fov = (this.camera.fov * Math.PI) / 180; + const height = 2 * Math.tan(fov / 2) * this.camera.position.z; + const width = height * this.camera.aspect; + this.viewport = { width, height }; + if (this.medias) { + this.medias.forEach(media => media.onResize({ screen: this.screen, viewport: this.viewport })); + } + } + update() { + this.scroll.current = lerp(this.scroll.current, this.scroll.target, this.scroll.ease); + const direction = this.scroll.current > this.scroll.last ? 'right' : 'left'; + if (this.medias) { + this.medias.forEach(media => media.update(this.scroll, direction)); + } + this.renderer.render({ scene: this.scene, camera: this.camera }); + this.scroll.last = this.scroll.current; + this.raf = window.requestAnimationFrame(this.update.bind(this)); + } + addEventListeners() { + this.boundOnResize = this.onResize.bind(this); + this.boundOnWheel = this.onWheel.bind(this); + this.boundOnTouchDown = this.onTouchDown.bind(this); + this.boundOnTouchMove = this.onTouchMove.bind(this); + this.boundOnTouchUp = this.onTouchUp.bind(this); + window.addEventListener('resize', this.boundOnResize); + window.addEventListener('mousewheel', this.boundOnWheel); + window.addEventListener('wheel', this.boundOnWheel); + window.addEventListener('mousedown', this.boundOnTouchDown); + window.addEventListener('mousemove', this.boundOnTouchMove); + window.addEventListener('mouseup', this.boundOnTouchUp); + window.addEventListener('touchstart', this.boundOnTouchDown); + window.addEventListener('touchmove', this.boundOnTouchMove); + window.addEventListener('touchend', this.boundOnTouchUp); + } + destroy() { + window.cancelAnimationFrame(this.raf); + window.removeEventListener('resize', this.boundOnResize); + window.removeEventListener('mousewheel', this.boundOnWheel); + window.removeEventListener('wheel', this.boundOnWheel); + window.removeEventListener('mousedown', this.boundOnTouchDown); + window.removeEventListener('mousemove', this.boundOnTouchMove); + window.removeEventListener('mouseup', this.boundOnTouchUp); + window.removeEventListener('touchstart', this.boundOnTouchDown); + window.removeEventListener('touchmove', this.boundOnTouchMove); + window.removeEventListener('touchend', this.boundOnTouchUp); + if (this.renderer && this.renderer.gl && this.renderer.gl.canvas.parentNode) { + this.renderer.gl.canvas.parentNode.removeChild(this.renderer.gl.canvas); + } + } +} + +// Ensure CircularGallery is properly configured and functional + +export default function CircularGallery({ + items, + bend = 3, + textColor = '#ffffff', + borderRadius = 0.05, + font = 'bold 30px Figtree', + scrollSpeed = 2, + scrollEase = 0.05 +}) { + const containerRef = useRef(null); + useEffect(() => { + const app = new App(containerRef.current, { items, bend, textColor, borderRadius, font, scrollSpeed, scrollEase }); + return () => { + app.destroy(); + }; + }, [items, bend, textColor, borderRadius, font, scrollSpeed, scrollEase]); + return
; +} diff --git a/src/components/CompilationBar.jsx b/src/components/CompilationBar.jsx new file mode 100644 index 0000000..839e485 --- /dev/null +++ b/src/components/CompilationBar.jsx @@ -0,0 +1,89 @@ +import React, { useState, useEffect } from 'react'; + + +const messages = [ + { threshold: 98, text: "BUILD SUCCESSFUL. Ready to deploy." }, + { threshold: 88, text: "Rendering visual assets..." }, + { threshold: 75, text: "Parsing user_feedback.log..." }, + { threshold: 55, text: "Fetching legacy_pointers (Ex-Heads)..." }, + { threshold: 35, text: "Compiling active_initiatives..." }, + { threshold: 15, text: "Importing libraries..." }, + { threshold: 0, text: "Initializing CodeChef_ABESEC.exe..." }, +]; + +const CompilationBar = () => { + const [scrollPercent, setScrollPercent] = useState(0); + const [statusMessage, setStatusMessage] = useState(messages[messages.length - 1].text); + + useEffect(() => { + let ticking = false; + + const handleScroll = () => { + + if (!ticking) { + window.requestAnimationFrame(() => { + + const totalHeight = document.documentElement.scrollHeight - window.innerHeight; + const currentScroll = window.scrollY; + + const percent = Math.min((currentScroll / totalHeight) * 100, 100); + + setScrollPercent(percent); + + const currentMsg = messages.find(msg => percent >= msg.threshold); + if (currentMsg) { + setStatusMessage(currentMsg.text); + } + + ticking = false; + }); + + ticking = true; + } + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return ( +
+ + {/* 1. Terminal Prompt */} +
+ + ~/codechef-abesec/site + | + {/* Added a min-width to prevent jitter when text length changes */} + + {statusMessage} + +
+ + {/* 2. Percentage */} +
+ [ {Math.round(scrollPercent)}% ] +
+ + {/* 3. Progress Bar Overlay */} +
+ +
+
+ ); +}; + +export default CompilationBar; \ No newline at end of file diff --git a/src/components/ExPresident.jsx b/src/components/ExPresident.jsx index bad6868..1e306d7 100644 --- a/src/components/ExPresident.jsx +++ b/src/components/ExPresident.jsx @@ -200,14 +200,16 @@ const ExPresident = () => { transformStyle: "preserve-3d", }} > -
+
{president.name} -
+
+ +

{president.name}

diff --git a/src/components/FloatingNavbar.jsx b/src/components/FloatingNavbar.jsx index d2729a6..3a7b77a 100644 --- a/src/components/FloatingNavbar.jsx +++ b/src/components/FloatingNavbar.jsx @@ -13,17 +13,17 @@ export const FloatingNav = ({ navItems, className }) => { const getActiveIndex = () => { const currentPath = location.pathname; - + // Check for exact match first const exactIndex = navItems.findIndex(item => item.href === currentPath); if (exactIndex !== -1) return exactIndex; - + // Check for sub-routes (e.g., /events/sub-event should highlight Events) const parentIndex = navItems.findIndex(item => { if (item.href === '/') return false; // Don't match home for sub-routes return currentPath.startsWith(item.href); }); - + return parentIndex !== -1 ? parentIndex : 0; }; @@ -50,7 +50,7 @@ export const FloatingNav = ({ navItems, className }) => { { initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} - className="fixed inset-0 z-40 bg-black/95 md:hidden" + className="fixed inset-0 z-40000 bg-black/95 md:hidden" onClick={() => setIsOpen(false)} > +
+ +
- - - -
-
- -
- -
- +
+ + + + + + +
@@ -165,7 +178,7 @@ export default function Landing() { initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ delay: 0.5, duration: 0.6 }} - className="flex flex-wrap justify-center lg:justify-start gap-4 -mt-10" + className="flex flex-wrap justify-center gap-4 -mt-10" >
); diff --git a/src/components/LightRays.jsx b/src/components/LightRays.jsx new file mode 100644 index 0000000..59386b5 --- /dev/null +++ b/src/components/LightRays.jsx @@ -0,0 +1,399 @@ +import { useRef, useEffect, useState } from 'react'; +import { Renderer, Program, Triangle, Mesh } from 'ogl'; + +const DEFAULT_COLOR = '#ffffff'; + +const hexToRgb = hex => { + const m = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); + return m ? [parseInt(m[1], 16) / 255, parseInt(m[2], 16) / 255, parseInt(m[3], 16) / 255] : [1, 1, 1]; +}; + +const getAnchorAndDir = (origin, w, h) => { + const outside = 0.2; + switch (origin) { + case 'top-left': + return { anchor: [0, -outside * h], dir: [0, 1] }; + case 'top-right': + return { anchor: [w, -outside * h], dir: [0, 1] }; + case 'left': + return { anchor: [-outside * w, 0.5 * h], dir: [1, 0] }; + case 'right': + return { anchor: [(1 + outside) * w, 0.5 * h], dir: [-1, 0] }; + case 'bottom-left': + return { anchor: [0, (1 + outside) * h], dir: [0, -1] }; + case 'bottom-center': + return { anchor: [0.5 * w, (1 + outside) * h], dir: [0, -1] }; + case 'bottom-right': + return { anchor: [w, (1 + outside) * h], dir: [0, -1] }; + default: // "top-center" + return { anchor: [0.5 * w, -outside * h], dir: [0, 1] }; + } +}; + +const LightRays = ({ + raysOrigin = 'top-center', + raysColor = DEFAULT_COLOR, + raysSpeed = 1, + lightSpread = 1, + rayLength = 2, + pulsating = false, + fadeDistance = 1.0, + saturation = 1.0, + followMouse = true, + mouseInfluence = 0.1, + noiseAmount = 0.0, + distortion = 0.0, + className = '' +}) => { + const containerRef = useRef(null); + const uniformsRef = useRef(null); + const rendererRef = useRef(null); + const mouseRef = useRef({ x: 0.5, y: 0.5 }); + const smoothMouseRef = useRef({ x: 0.5, y: 0.5 }); + const animationIdRef = useRef(null); + const meshRef = useRef(null); + const cleanupFunctionRef = useRef(null); + const [isVisible, setIsVisible] = useState(false); + const observerRef = useRef(null); + + useEffect(() => { + if (!containerRef.current) return; + + observerRef.current = new IntersectionObserver( + entries => { + const entry = entries[0]; + setIsVisible(entry.isIntersecting); + }, + { threshold: 0.1 } + ); + + observerRef.current.observe(containerRef.current); + + return () => { + if (observerRef.current) { + observerRef.current.disconnect(); + observerRef.current = null; + } + }; + }, []); + + useEffect(() => { + if (!isVisible || !containerRef.current) return; + + if (cleanupFunctionRef.current) { + cleanupFunctionRef.current(); + cleanupFunctionRef.current = null; + } + + const initializeWebGL = async () => { + if (!containerRef.current) return; + + await new Promise(resolve => setTimeout(resolve, 10)); + + if (!containerRef.current) return; + + const renderer = new Renderer({ + dpr: Math.min(window.devicePixelRatio, 2), + alpha: true + }); + rendererRef.current = renderer; + + const gl = renderer.gl; + gl.canvas.style.width = '100%'; + gl.canvas.style.height = '100%'; + + while (containerRef.current.firstChild) { + containerRef.current.removeChild(containerRef.current.firstChild); + } + containerRef.current.appendChild(gl.canvas); + + const vert = ` +attribute vec2 position; +varying vec2 vUv; +void main() { + vUv = position * 0.5 + 0.5; + gl_Position = vec4(position, 0.0, 1.0); +}`; + + const frag = `precision highp float; + +uniform float iTime; +uniform vec2 iResolution; + +uniform vec2 rayPos; +uniform vec2 rayDir; +uniform vec3 raysColor; +uniform float raysSpeed; +uniform float lightSpread; +uniform float rayLength; +uniform float pulsating; +uniform float fadeDistance; +uniform float saturation; +uniform vec2 mousePos; +uniform float mouseInfluence; +uniform float noiseAmount; +uniform float distortion; + +varying vec2 vUv; + +float noise(vec2 st) { + return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123); +} + +float rayStrength(vec2 raySource, vec2 rayRefDirection, vec2 coord, + float seedA, float seedB, float speed) { + vec2 sourceToCoord = coord - raySource; + vec2 dirNorm = normalize(sourceToCoord); + float cosAngle = dot(dirNorm, rayRefDirection); + + float distortedAngle = cosAngle + distortion * sin(iTime * 2.0 + length(sourceToCoord) * 0.01) * 0.2; + + float spreadFactor = pow(max(distortedAngle, 0.0), 1.0 / max(lightSpread, 0.001)); + + float distance = length(sourceToCoord); + float maxDistance = iResolution.x * rayLength; + float lengthFalloff = clamp((maxDistance - distance) / maxDistance, 0.0, 1.0); + + float fadeFalloff = clamp((iResolution.x * fadeDistance - distance) / (iResolution.x * fadeDistance), 0.5, 1.0); + float pulse = pulsating > 0.5 ? (0.8 + 0.2 * sin(iTime * speed * 3.0)) : 1.0; + + float baseStrength = clamp( + (0.45 + 0.15 * sin(distortedAngle * seedA + iTime * speed)) + + (0.3 + 0.2 * cos(-distortedAngle * seedB + iTime * speed)), + 0.0, 1.0 + ); + + return baseStrength * lengthFalloff * fadeFalloff * spreadFactor * pulse; +} + +void mainImage(out vec4 fragColor, in vec2 fragCoord) { + vec2 coord = vec2(fragCoord.x, iResolution.y - fragCoord.y); + + vec2 finalRayDir = rayDir; + if (mouseInfluence > 0.0) { + vec2 mouseScreenPos = mousePos * iResolution.xy; + vec2 mouseDirection = normalize(mouseScreenPos - rayPos); + finalRayDir = normalize(mix(rayDir, mouseDirection, mouseInfluence)); + } + + vec4 rays1 = vec4(1.0) * + rayStrength(rayPos, finalRayDir, coord, 36.2214, 21.11349, + 1.5 * raysSpeed); + vec4 rays2 = vec4(1.0) * + rayStrength(rayPos, finalRayDir, coord, 22.3991, 18.0234, + 1.1 * raysSpeed); + + fragColor = rays1 * 0.5 + rays2 * 0.4; + + if (noiseAmount > 0.0) { + float n = noise(coord * 0.01 + iTime * 0.1); + fragColor.rgb *= (1.0 - noiseAmount + noiseAmount * n); + } + + float brightness = 1.0 - (coord.y / iResolution.y); + fragColor.x *= 0.1 + brightness * 0.8; + fragColor.y *= 0.3 + brightness * 0.6; + fragColor.z *= 0.5 + brightness * 0.5; + + if (saturation != 1.0) { + float gray = dot(fragColor.rgb, vec3(0.299, 0.587, 0.114)); + fragColor.rgb = mix(vec3(gray), fragColor.rgb, saturation); + } + + fragColor.rgb *= raysColor; +} + +void main() { + vec4 color; + mainImage(color, gl_FragCoord.xy); + gl_FragColor = color; +}`; + + const uniforms = { + iTime: { value: 0 }, + iResolution: { value: [1, 1] }, + + rayPos: { value: [0, 0] }, + rayDir: { value: [0, 1] }, + + raysColor: { value: hexToRgb(raysColor) }, + raysSpeed: { value: raysSpeed }, + lightSpread: { value: lightSpread }, + rayLength: { value: rayLength }, + pulsating: { value: pulsating ? 1.0 : 0.0 }, + fadeDistance: { value: fadeDistance }, + saturation: { value: saturation }, + mousePos: { value: [0.5, 0.5] }, + mouseInfluence: { value: mouseInfluence }, + noiseAmount: { value: noiseAmount }, + distortion: { value: distortion } + }; + uniformsRef.current = uniforms; + + const geometry = new Triangle(gl); + const program = new Program(gl, { vertex: vert, fragment: frag, uniforms }); + const mesh = new Mesh(gl, { geometry, program }); + meshRef.current = mesh; + + const updatePlacement = () => { + if (!containerRef.current || !renderer) return; + + renderer.dpr = Math.min(window.devicePixelRatio, 2); + + const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current; + renderer.setSize(wCSS, hCSS); + + const dpr = renderer.dpr; + const w = wCSS * dpr; + const h = hCSS * dpr; + + uniforms.iResolution.value = [w, h]; + + const { anchor, dir } = getAnchorAndDir(raysOrigin, w, h); + uniforms.rayPos.value = anchor; + uniforms.rayDir.value = dir; + }; + + const loop = t => { + if (!rendererRef.current || !uniformsRef.current || !meshRef.current) { + return; + } + + uniforms.iTime.value = t * 0.001; + + if (followMouse && mouseInfluence > 0.0) { + const smoothing = 0.92; + + smoothMouseRef.current.x = smoothMouseRef.current.x * smoothing + mouseRef.current.x * (1 - smoothing); + smoothMouseRef.current.y = smoothMouseRef.current.y * smoothing + mouseRef.current.y * (1 - smoothing); + + uniforms.mousePos.value = [smoothMouseRef.current.x, smoothMouseRef.current.y]; + } + + try { + renderer.render({ scene: mesh }); + animationIdRef.current = requestAnimationFrame(loop); + } catch (error) { + console.warn('WebGL rendering error:', error); + return; + } + }; + + window.addEventListener('resize', updatePlacement); + updatePlacement(); + animationIdRef.current = requestAnimationFrame(loop); + + cleanupFunctionRef.current = () => { + if (animationIdRef.current) { + cancelAnimationFrame(animationIdRef.current); + animationIdRef.current = null; + } + + window.removeEventListener('resize', updatePlacement); + + if (renderer) { + try { + const canvas = renderer.gl.canvas; + const loseContextExt = renderer.gl.getExtension('WEBGL_lose_context'); + if (loseContextExt) { + loseContextExt.loseContext(); + } + + if (canvas && canvas.parentNode) { + canvas.parentNode.removeChild(canvas); + } + } catch (error) { + console.warn('Error during WebGL cleanup:', error); + } + } + + rendererRef.current = null; + uniformsRef.current = null; + meshRef.current = null; + }; + }; + + initializeWebGL(); + + return () => { + if (cleanupFunctionRef.current) { + cleanupFunctionRef.current(); + cleanupFunctionRef.current = null; + } + }; + }, [ + isVisible, + raysOrigin, + raysColor, + raysSpeed, + lightSpread, + rayLength, + pulsating, + fadeDistance, + saturation, + followMouse, + mouseInfluence, + noiseAmount, + distortion + ]); + + useEffect(() => { + if (!uniformsRef.current || !containerRef.current || !rendererRef.current) return; + + const u = uniformsRef.current; + const renderer = rendererRef.current; + + u.raysColor.value = hexToRgb(raysColor); + u.raysSpeed.value = raysSpeed; + u.lightSpread.value = lightSpread; + u.rayLength.value = rayLength; + u.pulsating.value = pulsating ? 1.0 : 0.0; + u.fadeDistance.value = fadeDistance; + u.saturation.value = saturation; + u.mouseInfluence.value = mouseInfluence; + u.noiseAmount.value = noiseAmount; + u.distortion.value = distortion; + + const { clientWidth: wCSS, clientHeight: hCSS } = containerRef.current; + const dpr = renderer.dpr; + const { anchor, dir } = getAnchorAndDir(raysOrigin, wCSS * dpr, hCSS * dpr); + u.rayPos.value = anchor; + u.rayDir.value = dir; + }, [ + raysColor, + raysSpeed, + lightSpread, + raysOrigin, + rayLength, + pulsating, + fadeDistance, + saturation, + mouseInfluence, + noiseAmount, + distortion + ]); + + useEffect(() => { + const handleMouseMove = e => { + if (!containerRef.current || !rendererRef.current) return; + const rect = containerRef.current.getBoundingClientRect(); + const x = (e.clientX - rect.left) / rect.width; + const y = (e.clientY - rect.top) / rect.height; + mouseRef.current = { x, y }; + }; + + if (followMouse) { + window.addEventListener('mousemove', handleMouseMove); + return () => window.removeEventListener('mousemove', handleMouseMove); + } + }, [followMouse]); + + return ( +
+ ); +}; + +export default LightRays; diff --git a/src/components/Offering.jsx b/src/components/Offering.jsx index f0b58ef..264f659 100644 --- a/src/components/Offering.jsx +++ b/src/components/Offering.jsx @@ -1,99 +1,336 @@ -import React from "react"; +import React, { useState, useEffect, useRef } from "react"; import "../App.css"; -import { PinContainer } from "./PinContainer"; const Offering = () => { + const [activeIndex, setActiveIndex] = useState(0); + const totalItems = 4; + const baseRotation = -5; + const itemSpacing = 12; + const rotateVal = -(activeIndex * itemSpacing); + + const contentData = [ + { + title: "Coding\nWorkSpaces", + desc: "Immersive coding environments designed for focus and productivity. We host competitive programming workshops, hackathons, and hands-on bootcamps to sharpen your skills.", + img: "/OUAC2.webp" + }, + { + title: "Connect With\nCool Mentors", + desc: "Bridge the gap between learning and industry. Connect with experienced developers, alumni, and tech leaders who provide guidance, code reviews, and career advice.", + img: "/rust7.webp" + }, + { + title: "Innovation\nHub", + desc: "A collaborative space where wild ideas turn into reality. We provide the resources, peer support, and brainstorming sessions needed to launch your next big project.", + img: "/COC3.JPG" + }, + { + title: "Community\nEvents", + desc: "Join a thriving network of tech enthusiasts. From casual meetups to tech talks and networking nights, we foster a culture of sharing knowledge and growing together.", + img: "/COC1.JPG" + }, + ]; + + // useEffect(() => { + // const interval = setInterval(() => { + // setActiveIndex((prev) => (prev + 1) % totalItems); + // }, 4000); + // return () => clearInterval(interval); + // }, []); + + const [slideAmount, setSlideAmount] = useState(300); + const [hoveredIndex, setHoveredIndex] = useState(null); + const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 }); + + const [isMobile, setIsMobile] = useState(false); + const [wheelConfig, setWheelConfig] = useState({ + radius: 510, + rotateOffset: -250, + scale: 0.9 + }); + + const handleMouseMove = (e) => { + setMousePosition({ x: e.clientX, y: e.clientY }); + }; + + useEffect(() => { + const handleResize = () => { + if (window.innerWidth >= 1024) { + setSlideAmount(632); + setIsMobile(false); + setWheelConfig({ radius: 510, rotateOffset: -250, scale: 0.9 }); + } else if (window.innerWidth >= 768) { + setSlideAmount(375); + setIsMobile(false); + setWheelConfig({ radius: 450, rotateOffset: -200, scale: 0.8 }); + } else { + setSlideAmount(200); + setIsMobile(true); + setWheelConfig({ radius: 250, rotateOffset: -50, scale: 0.55 }); + } + }; + + + handleResize(); + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); + + const handleItemClick = (index) => { + setActiveIndex(index); + }; + + const handleNext = () => { + setActiveIndex((prev) => (prev + 1) % totalItems); + }; + + const handlePrev = () => { + setActiveIndex((prev) => (prev - 1 + totalItems) % totalItems); + }; + return ( -
-
- - -
-

- Our Initiatives -

- -

- Empowering students to code, collaborate, and create impactful tech solutions. -
- We are building a community where curiosity meets execution. Through hands on programs, mentorship, and innovation driven events, we help students transform ideas into real world solutions. -

-
- - -
- - - -
-

- Coding WorkSpaces & Upskill -

- -
- - We host coding workshops for hands on practice for competitive programming. - -
- - - Coding Workspaces -
-
- - - - -
- -

- Connect With Cool Mentors -

- -
- - Connect with Experienced mentors, seniors and explore new perspectives. - -
- - Mentors -
-
- - - - -
- -

- Innovation Hub -

- -
- - Unlocking the potential of innovations and collaborating ideas and minds. - +
+
+
+ +
- - Innovation Hub -
- -
+
+
+
+ +
+ +
+
+ + +
+ + + {["Upskill", "Mentors", "Innovation", "Community"].map((item, idx) => { + return ( +
handleItemClick(idx)} + onMouseEnter={() => setHoveredIndex(idx)} + onMouseLeave={() => setHoveredIndex(null)} + className={`absolute left-[calc(50%+50px)] top-1/2 text-[12px] sm:text-[24px] md:text-[30px] lg:text-[38px] font-montserrat font-medium transition-all duration-1000 z-[100] group flex items-center gap-1 sm:gap-2 ${activeIndex === idx + ? "text-white" + : "text-white/50 hover:text-white/80" + }`} + style={{ + cursor: 'url("/wheel/hand.svg"), pointer', + transform: `rotate(${-8 + idx * 12}deg) translateX(${wheelConfig.radius}px) translateY(-50%)`, + transformOrigin: "left center", + }} + > + + {item} + +
+ ); + })} +
+
+ + + +
+

+ Initiatives +

+
+ +
+
+ {contentData.map((content, idx) => ( +
+

+ {content.title} +

+

+ {content.desc} +

+
+ ))} +
+
+ + + + + + + + + +
+
+
+ Hands Line +
+
+ Hands Line +
+
+
-
+ +
+
+
+ + + + { + isMobile ? ( +
+ Active Event +
+ ) : ( + hoveredIndex !== null && contentData[hoveredIndex] && ( +
+ Hover Preview +
+ ) + ) + } + ); }; diff --git a/src/components/PinContainer.jsx b/src/components/PinContainer.jsx deleted file mode 100644 index 6c93a86..0000000 --- a/src/components/PinContainer.jsx +++ /dev/null @@ -1,186 +0,0 @@ -"use client"; -import React, { useState, useEffect } from "react"; -import { motion } from "motion/react"; -import { cn } from "../lib/utils"; - -export const PinContainer = ({ - children, - title, - href, - className, - containerClassName, -}) => { - // 1. Add state to track mobile view - const [isMobile, setIsMobile] = useState(false); - const [transform, setTransform] = useState( - "translate(-50%,-50%) rotateX(25deg) scale(0.72)" - ); - - // 2. Check screen size on mount and resize - useEffect(() => { - const checkMobile = () => { - const isTouch = window.innerWidth < 768; // Tailwind 'md' breakpoint - setIsMobile(isTouch); - - // Force the correct initial transform based on screen size - if (isTouch) { - setTransform("translate(-50%,-50%) rotateX(0deg) scale(1)"); - } else { - setTransform("translate(-50%,-50%) rotateX(25deg) scale(0.72)"); - } - }; - - checkMobile(); - window.addEventListener("resize", checkMobile); - return () => window.removeEventListener("resize", checkMobile); - }, []); - - const onMouseEnter = () => { - // Only apply the 3D hover effect on Desktop - if (!isMobile) { - setTransform("translate(-50%,-50%) rotateX(0deg) scale(0.9)"); - } - }; - - const onMouseLeave = () => { - // Reset to appropriate resting state based on device - if (!isMobile) { - setTransform("translate(-50%,-50%) rotateX(25deg) scale(0.72)"); - } else { - setTransform("translate(-50%,-50%) rotateX(0deg) scale(1)"); - } - }; - - return ( - -
-
-
{children}
-
-
- -
- ); -}; - -export const PinPerspective = ({ title, href }) => { - return ( - -
- - -
- <> - - - - -
- - <> - - - - - -
-
- ); -}; \ No newline at end of file diff --git a/src/components/RippleGridBackground.jsx b/src/components/RippleGridBackground.jsx new file mode 100644 index 0000000..2140129 --- /dev/null +++ b/src/components/RippleGridBackground.jsx @@ -0,0 +1,148 @@ +import React, { useEffect, useRef } from 'react'; + +// Helper for smooth animations +const lerp = (start, end, amt) => (1 - amt) * start + amt * end; + +const RippleGridBackground = () => { + const canvasRef = useRef(null); + + const config = { + // VISUAL SETTINGS + gridSize: 40, // Slightly larger squares for a more modern look + gap: 0, // No gap = seamless grid (looks more like a surface) + + // COLOR GRADIENT (HSL) + // Base: The resting state (Almost invisible) + baseHue: 120, // CodeChef Greenish + baseSat: 15, // Very desaturated + baseLight: 2, // 2% lightness (Very, very dark) + + // Active: The ripple state (Subtle glow) + activeHue: 150, // Shifts to Teal/Cyan when hovered + activeSat: 30, // Slightly more color + activeLight: 10, // Only goes up to 10% brightness (Subtle!) + + // ANIMATION PHYSICS + mouseRadius: 400, // Larger radius for a smoother gradient spread + lerpFactor: 0.05, // 0.05 is very slow and smooth (liquid feel) + }; + + useEffect(() => { + const canvas = canvasRef.current; + const ctx = canvas.getContext('2d'); + let animationFrameId; + let width, height, rows, cols; + + let grid = []; + let mouse = { x: -1000, y: -1000 }; + + const resize = () => { + width = window.innerWidth; + height = window.innerHeight; + canvas.width = width; + canvas.height = height; + + const cellSize = config.gridSize + config.gap; + cols = Math.ceil(width / cellSize); + rows = Math.ceil(height / cellSize); + + grid = []; + for (let i = 0; i < cols; i++) { + grid[i] = []; + for (let j = 0; j < rows; j++) { + grid[i][j] = { + intensity: 0, // 0 = Base State, 1 = Active State + }; + } + } + }; + + const handleMouseMove = (e) => { + mouse.x = e.clientX; + mouse.y = e.clientY; + }; + + const handleMouseLeave = () => { + mouse.x = -1000; + mouse.y = -1000; + }; + + window.addEventListener('resize', resize); + window.addEventListener('mousemove', handleMouseMove); + document.addEventListener('mouseleave', handleMouseLeave); + + resize(); + + const render = () => { + // Clear background with the base color + ctx.fillStyle = `hsl(${config.baseHue}, ${config.baseSat}%, ${config.baseLight}%)`; + ctx.fillRect(0, 0, width, height); + + const cellSize = config.gridSize + config.gap; + + for (let i = 0; i < cols; i++) { + for (let j = 0; j < rows; j++) { + const cell = grid[i][j]; + + const cellX = i * cellSize; + const cellY = j * cellSize; + const centerX = cellX + config.gridSize / 2; + const centerY = cellY + config.gridSize / 2; + + const dist = Math.hypot(mouse.x - centerX, mouse.y - centerY); + + // Calculate Target Intensity + let target = 0; + if (dist < config.mouseRadius) { + const normDist = dist / config.mouseRadius; + // Cubic easing (1 - d)^3 for a much softer, gradient-like falloff + target = Math.pow(1 - normDist, 3); + } + + // Smooth interpolation + cell.intensity = lerp(cell.intensity, target, config.lerpFactor); + + // Optimization: Only draw cells that aren't purely base state + if (cell.intensity > 0.001) { + // Interpolate Colors (Create the gradient transition) + const h = lerp(config.baseHue, config.activeHue, cell.intensity); + const s = lerp(config.baseSat, config.activeSat, cell.intensity); + const l = lerp(config.baseLight, config.activeLight, cell.intensity); + + ctx.fillStyle = `hsl(${h}, ${s}%, ${l}%)`; + + // No scale effect - simpler, cleaner, less distracting + ctx.fillRect(cellX, cellY, config.gridSize, config.gridSize); + } + } + } + animationFrameId = requestAnimationFrame(render); + }; + + render(); + + return () => { + window.removeEventListener('resize', resize); + window.removeEventListener('mousemove', handleMouseMove); + document.removeEventListener('mouseleave', handleMouseLeave); + cancelAnimationFrame(animationFrameId); + }; + }, []); + + return ( + + ); +}; + +export default RippleGridBackground; \ No newline at end of file diff --git a/src/components/ShuffleText2.jsx b/src/components/ShuffleText2.jsx index 531c099..7ff9291 100644 --- a/src/components/ShuffleText2.jsx +++ b/src/components/ShuffleText2.jsx @@ -91,7 +91,8 @@ const ShuffleText2 = ({ text, className }) => { opacity: 1, y: 0, transition: { - duration: 0.3 + duration: 0.5, + ease: "easeOut" } }} style={{ display: 'inline-block', whiteSpace: 'pre' }} diff --git a/src/components/TeamGrid.jsx b/src/components/TeamGrid.jsx index be9008e..3b1dd34 100644 --- a/src/components/TeamGrid.jsx +++ b/src/components/TeamGrid.jsx @@ -1,5 +1,6 @@ import { useEffect, useState, useRef } from "react"; import { useNavigate } from "react-router-dom"; +import { motion } from "framer-motion"; import { IconHome, IconCalendar, @@ -18,23 +19,23 @@ export default function TeamGrid() { const navigate = useNavigate(); const teamMembers = [ - { name: 'Vishesh Dudeja', role: 'Advisory', row: 0, col: 0, transformOrigin: 'right bottom',img:'1.webp', linkedin: 'https://linkedin.com/in/vishesh-dudeja-b62a79242', tech: ['Python', 'Java', 'SQL'] }, - { name: 'Sai Aryan Goswami', role: 'Core Team', row: 0, col: 2, transformOrigin: 'left bottom',img:'2.webp', linkedin: 'https://linkedin.com/in/saiaryangoswami', tech: ['UI/UX', 'Next.js', 'Full Stack'] }, - { name: 'Vidhi Gandhi', role: 'President', row: 1, col: 1, transformOrigin: 'left bottom',img:'vidhi-didi.jpg', linkedin: 'https://linkedin.com/in/vidhi-gandhi-640806296', tech: ['React.js', 'HTML', 'CSS'] }, - { name: 'Rohit Bhardwaj', role: 'Vice-president', row: 2, col: 0, transformOrigin: 'right bottom',img:'rohit-bhaiya.jpg', linkedin: 'https://linkedin.com/in/dev-rohitbhardwaj', tech: ['Solidity', 'Web3', 'Open Source'] }, - { name: 'Lavish Aggarwal', role: 'Vice-president', row: 2, col: 3, transformOrigin: 'left bottom', img:'lavish-bhaiya.jpg', linkedin: 'https://linkedin.com/in/lavishagrwl', tech: ['JavaScript', 'Python', 'React.js'] }, - { name: 'Abhinav Vishwakarma', role: 'Development Lead', row: 3, col: 1, transformOrigin: 'left bottom',img:'abhinav-bhaiya.jpg', linkedin: 'https://linkedin.com/in/abhinav-vishwakarma-fsd', tech: ['Canva', 'Python', 'Video Editing'] }, - { name: 'Ramyak Jain', role: 'Event Lead', row: 3, col: 2, transformOrigin: 'right bottom', img:'ramayk bhaiya.jpg', linkedin: 'https://linkedin.com/in/ramyak-jain', tech: ['Graphic Designer', 'Content Writing', 'Wed Designer'] }, - { name: 'Utkarsh Saxena', role: 'Cp Lead',row: 4, col: 0, transformOrigin: 'left bottom',img:'utkarsh-bhaiya.jpg', linkedin: 'https://linkedin.com/in/utkarsh-saxena-91005a290', tech: ['React.js', 'Javascript', 'DSA'] }, - { name: 'Deepanshu', role: 'Graphics Lead', row: 4, col: 3, transformOrigin: 'left bottom',img:'deepanshu bhaiya.jpg', linkedin: 'https://linkedin.com/in/deepanshu-kaushik-174059297', tech: ['UI/UX Designer', 'DSA', 'Javascript'] }, - { name: 'Swati Mittal', role: 'Pr Lead', row: 5, col: 1, transformOrigin: 'left bottom',img:'swati-didi.jpg', linkedin: 'https://linkedin.com/in/swati-mittal24', tech: ['MongoDB', 'Node.js', 'express.js'] }, - { name: 'Ananya', role: 'Content Lead', row: 6, col: 1, transformOrigin: 'left bottom',img:'ananya-didi.jpg', linkedin: 'https://linkedin.com/in/', tech: ['Copywriting', 'SEO', 'Content Strategy'] }, - { name: 'Sakhi Vishnoi', role: 'Graphics Lead', row: 6, col: 3, transformOrigin: 'left bottom', img:'sakshi didi.jpg', linkedin: 'https://linkedin.com/in/sakshi-vishnoi-7770b2315', tech: ['Flutter', 'DSA', 'C++'] }, - { name: 'Anvesh Srivastava ', role: 'Backend Developer', row: 7, col: 0, transformOrigin: 'right bottom',img:'anvesh-bhaiya.jpg', linkedin: 'https://linkedin.com/in/anvesh-srivastava', tech: ['Node.js', 'RestAPI', 'MongoDB'] }, - { name: 'kaif azmi', role: 'Frontend Developer', row: 7, col: 2, transformOrigin: 'left bottom',img:'kaif-bhaiya.jpg', linkedin: 'https://linkedin.com/in/kaifazmi', tech: ['React', 'CSS', 'JavaScript'] }, - { name: "Bhaskar Dwivedi", role: 'Mobile Developer', row: 8, col: 1, transformOrigin: 'left bottom',img:'bhaskar-bhaiya.jpg', linkedin: 'https://linkedin.com/in/bhaskar-dwi', tech: ['React Native', 'Flutter', 'TailwidCSS'] }, - { name: 'Dhruv Khare', role: 'Design Systems', row: 9, col: 0, transformOrigin: 'right bottom',img:'dhruv-bhaiya.jpg', linkedin: 'https://linkedin.com/in/dhruvkhare-softwaredev', tech: ['RestAPI', 'MonogoDB', 'Express.js'] }, - { name: 'Amit Gupta', role: 'Product Analyst', row: 9, col: 3, transformOrigin: 'left bottom', img:'amit bhaiya.png', linkedin: 'linkedin.com/in/amitguptadev', tech: ['Node.js', 'AI/ML', 'Typescript'] } + { name: 'Vishesh Dudeja', role: 'Advisory', row: 0, col: 0, transformOrigin: 'right bottom', img: '1.webp', linkedin: 'https://linkedin.com/in/vishesh-dudeja-b62a79242', tech: ['Python', 'Java', 'SQL'] }, + { name: 'Sai Aryan Goswami', role: 'Core Team', row: 0, col: 2, transformOrigin: 'left bottom', img: '2.webp', linkedin: 'https://linkedin.com/in/saiaryangoswami', tech: ['UI/UX', 'Next.js', 'Full Stack'] }, + { name: 'Vidhi Gandhi', role: 'President', row: 1, col: 1, transformOrigin: 'left bottom', img: 'vidhi-didi.jpg', linkedin: 'https://linkedin.com/in/vidhi-gandhi-640806296', tech: ['React.js', 'HTML', 'CSS'] }, + { name: 'Rohit Bhardwaj', role: 'Vice-president', row: 2, col: 0, transformOrigin: 'right bottom', img: 'rohit-bhaiya.jpg', linkedin: 'https://linkedin.com/in/dev-rohitbhardwaj', tech: ['Solidity', 'Web3', 'Open Source'] }, + { name: 'Lavish Aggarwal', role: 'Vice-president', row: 2, col: 3, transformOrigin: 'left bottom', img: 'lavish-bhaiya.jpg', linkedin: 'https://linkedin.com/in/lavishagrwl', tech: ['JavaScript', 'Python', 'React.js'] }, + { name: 'Abhinav Vishwakarma', role: 'Development Lead', row: 3, col: 1, transformOrigin: 'left bottom', img: 'abhinav-bhaiya.jpg', linkedin: 'https://linkedin.com/in/abhinav-vishwakarma-fsd', tech: ['Canva', 'Python', 'Video Editing'] }, + { name: 'Ramyak Jain', role: 'Event Lead', row: 3, col: 2, transformOrigin: 'right bottom', img: 'ramayk bhaiya.jpg', linkedin: 'https://linkedin.com/in/ramyak-jain', tech: ['Graphic Designer', 'Content Writing', 'Wed Designer'] }, + { name: 'Utkarsh Saxena', role: 'Cp Lead', row: 4, col: 0, transformOrigin: 'left bottom', img: 'utkarsh-bhaiya.jpg', linkedin: 'https://linkedin.com/in/utkarsh-saxena-91005a290', tech: ['React.js', 'Javascript', 'DSA'] }, + { name: 'Deepanshu', role: 'Graphics Lead', row: 4, col: 3, transformOrigin: 'left bottom', img: 'deepanshu bhaiya.jpg', linkedin: 'https://linkedin.com/in/deepanshu-kaushik-174059297', tech: ['UI/UX Designer', 'DSA', 'Javascript'] }, + { name: 'Swati Mittal', role: 'Pr Lead', row: 5, col: 1, transformOrigin: 'left bottom', img: 'swati-didi.jpg', linkedin: 'https://linkedin.com/in/swati-mittal24', tech: ['MongoDB', 'Node.js', 'express.js'] }, + { name: 'Ananya', role: 'Content Lead', row: 6, col: 1, transformOrigin: 'left bottom', img: 'ananya-didi.jpg', linkedin: 'https://linkedin.com/in/', tech: ['Copywriting', 'SEO', 'Content Strategy'] }, + { name: 'Sakhi Vishnoi', role: 'Graphics Lead', row: 6, col: 3, transformOrigin: 'left bottom', img: 'sakshi didi.jpg', linkedin: 'https://linkedin.com/in/sakshi-vishnoi-7770b2315', tech: ['Flutter', 'DSA', 'C++'] }, + { name: 'Anvesh Srivastava ', role: 'Backend Developer', row: 7, col: 0, transformOrigin: 'right bottom', img: 'anvesh-bhaiya.jpg', linkedin: 'https://linkedin.com/in/anvesh-srivastava', tech: ['Node.js', 'RestAPI', 'MongoDB'] }, + { name: 'kaif azmi', role: 'Frontend Developer', row: 7, col: 2, transformOrigin: 'left bottom', img: 'kaif-bhaiya.jpg', linkedin: 'https://linkedin.com/in/kaifazmi', tech: ['React', 'CSS', 'JavaScript'] }, + { name: "Bhaskar Dwivedi", role: 'Mobile Developer', row: 8, col: 1, transformOrigin: 'left bottom', img: 'bhaskar-bhaiya.jpg', linkedin: 'https://linkedin.com/in/bhaskar-dwi', tech: ['React Native', 'Flutter', 'TailwidCSS'] }, + { name: 'Dhruv Khare', role: 'Design Systems', row: 9, col: 0, transformOrigin: 'right bottom', img: 'dhruv-bhaiya.jpg', linkedin: 'https://linkedin.com/in/dhruvkhare-softwaredev', tech: ['RestAPI', 'MonogoDB', 'Express.js'] }, + { name: 'Amit Gupta', role: 'Product Analyst', row: 9, col: 3, transformOrigin: 'left bottom', img: 'amit bhaiya.png', linkedin: 'linkedin.com/in/amitguptadev', tech: ['Node.js', 'AI/ML', 'Typescript'] } ]; useEffect(() => { @@ -139,7 +140,7 @@ export default function TeamGrid() { onClick: () => navigate("/admin"), }); } - + navLinks.push({ title: "Logout", icon: , @@ -153,17 +154,36 @@ export default function TeamGrid() {
-
- - scroll down to meet the teams - -
- -
-

Teams

-
+ +

+ {"Meet our Bawarchis".split("").map((char, index) => ( + + {char} + + ))} +

+
-
+
{grid.map((row, rowIndex) => (
{row.map((member, colIndex) => ( @@ -183,9 +203,8 @@ export default function TeamGrid() { > {member.name} @@ -194,11 +213,10 @@ export default function TeamGrid() { flex flex-col items-center justify-center gap-2 sm:gap-3 transition-opacity duration-300 - ${ - hoveredIndex === member.index - ? "opacity-100" - : "opacity-0 pointer-events-none" - } + ${hoveredIndex === member.index + ? "opacity-100" + : "opacity-0 pointer-events-none" + } `} >

@@ -239,8 +257,6 @@ export default function TeamGrid() {

))}
- -
); diff --git a/src/components/eventsGalary.jsx b/src/components/eventsGalary.jsx new file mode 100644 index 0000000..546b775 --- /dev/null +++ b/src/components/eventsGalary.jsx @@ -0,0 +1,20 @@ +import CircularGallery from './CircularGallery'; + +export default function EventsGalary() { + return ( +
+

+ Memories that we cooked together +

+
+ +
+
+ ); +} \ No newline at end of file diff --git a/src/components/requestAnimationFrame.jsx b/src/components/requestAnimationFrame.jsx new file mode 100644 index 0000000..98b6d21 --- /dev/null +++ b/src/components/requestAnimationFrame.jsx @@ -0,0 +1,93 @@ +import React, { useState, useEffect } from 'react'; + +// 1. Move static data OUTSIDE the component. +// Defined in reverse order (highest threshold first) to make the logic faster/simpler. +const messages = [ + { threshold: 98, text: "BUILD SUCCESSFUL. Ready to deploy." }, + { threshold: 88, text: "Rendering visual assets..." }, + { threshold: 75, text: "Parsing user_feedback.log..." }, + { threshold: 55, text: "Fetching legacy_pointers (Ex-Heads)..." }, + { threshold: 35, text: "Compiling active_initiatives..." }, + { threshold: 15, text: "Importing libraries..." }, + { threshold: 0, text: "Initializing CodeChef_ABESEC.exe..." }, +]; + +const CompilationBar = () => { + const [scrollPercent, setScrollPercent] = useState(0); + const [statusMessage, setStatusMessage] = useState(messages[messages.length - 1].text); + + useEffect(() => { + let ticking = false; + + const handleScroll = () => { + // 2. Use 'ticking' to prevent calculations if the browser is busy + if (!ticking) { + window.requestAnimationFrame(() => { + + const totalHeight = document.documentElement.scrollHeight - window.innerHeight; + const currentScroll = window.scrollY; + // Calculate percent + const percent = Math.min((currentScroll / totalHeight) * 100, 100); + + setScrollPercent(percent); + + // 3. Optimized search: Find the first message where percent > threshold + // Since array is reversed, this finds the highest applicable threshold immediately + const currentMsg = messages.find(msg => percent >= msg.threshold); + if (currentMsg) { + setStatusMessage(currentMsg.text); + } + + ticking = false; + }); + + ticking = true; + } + }; + + window.addEventListener('scroll', handleScroll); + return () => window.removeEventListener('scroll', handleScroll); + }, []); + + return ( +
+ + {/* 1. Terminal Prompt */} +
+ + ~/codechef-abesec/site + | + {/* Added a min-width to prevent jitter when text length changes */} + + {statusMessage} + +
+ + {/* 2. Percentage */} +
+ [ {Math.round(scrollPercent)}% ] +
+ + {/* 3. Progress Bar Overlay */} +
+ + {/* Optional: A thin bright line at the very top of the bar for extra "tech" feel */} +
+
+ ); +}; + +export default CompilationBar; \ No newline at end of file diff --git a/src/components/testimonial.tsx b/src/components/testimonial.tsx new file mode 100644 index 0000000..bf5bb1b --- /dev/null +++ b/src/components/testimonial.tsx @@ -0,0 +1,35 @@ +import { TestimonialsCard } from "./ui/testimonials-card" + +const testimonials = [ +{ + id: 1, + title: "Sarah Wilson", + description: "Incredible product! It exceeded all my expectations.", + image: "https://images.unsplash.com/photo-1494790108377-be9c29b29330?w=400&h=400&fit=crop", +}, +{ + id: 2, + title: "David Chen", + description: "The best investment I've made this year.", + image: "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=400&h=400&fit=crop", +}, +{ + id: 3, + title: "Emma Rodriguez", + description: "Outstanding service and amazing results!", + image: "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?w=400&h=400&fit=crop", +}, +]; + +export default function AnimatedTestimonialsDemo() { +return ( +
+

+ Served Hot: Words from Our Bawarchis +

+
+ +
+
+); +} \ No newline at end of file diff --git a/src/components/ui/infinite-menu.tsx b/src/components/ui/infinite-menu.tsx new file mode 100644 index 0000000..a238932 --- /dev/null +++ b/src/components/ui/infinite-menu.tsx @@ -0,0 +1,377 @@ +// @ts-check +import React, { useCallback, useMemo, useState, memo, useEffect } from "react"; +import { + View, + StyleSheet, + Dimensions, + Animated as RNAnimated, +} from "react-native"; +import { Canvas, Circle, Group, Image, Skia } from "@shopify/react-native-skia"; +import { useSharedValue, useFrameCallback } from "react-native-reanimated"; +import { + Gesture, + GestureDetector, + GestureHandlerRootView, +} from "react-native-gesture-handler"; +import { useLoadImages } from "./hooks"; +import { Quat, Vec3 } from "./maths-type"; +import type { IDisc, IDiscComponent, IInfiniteMenu, IMenuItem } from "./types"; +import { generateIcosahedronVertices } from "./helpers"; +import { + projectToSphere, + quatConjugate, + quatFromVectors, + quatMultiply, + quatNormalize, + quatRotateVec3, + quatSlerp, + vec3Normalize, +} from "./maths"; +import { scheduleOnRN } from "react-native-worklets"; + +const DiscComponent: React.FC = memo( + ({ + x, + y, + radius, + alpha, + image, + }: IDiscComponent): React.ReactElement | null => { + const clipPath = useMemo(() => { + const path = Skia.Path.Make(); + path.addCircle(x, y, radius); + return path; + }, [x, y, radius]); + + if (radius < 1) return null; + + if (!image) { + return ( + + ); + } + + return ( + + + + ); + }, +); + +export const InfiniteMenu: React.FC & + React.FunctionComponent = memo( + ({ items, scale = 1, backgroundColor = "#000000" }: IInfiniteMenu) => { + const { width: screenWidth, height: screenHeight } = + Dimensions.get("window"); + const centerX = screenWidth / 2; + const centerY = screenHeight / 2; + + const imageUrls = useMemo( + () => items.map((item) => item.image), + [items], + ); + const loadedImages = useLoadImages(imageUrls); + + const [activeItem, setActiveItem] = useState( + items[0] || null, + ); + const [isMoving, setIsMoving] = useState(false); + const [discData, setDiscData] = useState([]); + const SPHERE_RADIUS = 2 * scale; + const DISC_BASE_SCALE = 0.25; + const CAMERA_Z = 3 * scale; + const PROJECTION_SCALE = 150; + const sphereVertices = useMemo( + () => generateIcosahedronVertices(1, SPHERE_RADIUS), + [SPHERE_RADIUS], + ); + + const verticesRef = useMemo(() => [...sphereVertices], [sphereVertices]); + + const qx = useSharedValue(0); + const qy = useSharedValue(0); + const qz = useSharedValue(0); + const qw = useSharedValue(1); + + const prx = useSharedValue(0); + const pry = useSharedValue(0); + const prz = useSharedValue(0); + const prw = useSharedValue(1); + + const rotVelocity = useSharedValue(0); + const isDown = useSharedValue(false); + const prevX = useSharedValue(0); + const prevY = useSharedValue(0); + const camZ = useSharedValue(CAMERA_Z); + const activeIdx = useSharedValue(0); + + const updateActiveItem = useCallback( + (index: number) => { + if (items.length === 0) return; + const itemIndex = index % items.length; + setActiveItem(items[itemIndex]); + }, + [items], + ); + + const updateIsMoving = useCallback((moving: boolean) => { + setIsMoving(moving); + }, []); + + const updateDiscData = useCallback((data: IDisc[]) => { + setDiscData(data); + }, []); + + const lastMoving = useSharedValue(false); + const frameSkip = useSharedValue(0); + + useFrameCallback((info) => { + "worklet"; + // Clamp dt to prevent physics jumps after idle + const rawDt = info.timeSincePreviousFrame || 16; + const dt = Math.min(rawDt, 50); + const ts = dt / 16 + 0.0001; + const IDENTITY: Quat = { x: 0, y: 0, z: 0, w: 1 }; + + const orientation: Quat = { + x: qx.value, + y: qy.value, + z: qz.value, + w: qw.value, + }; + const pointerRot: Quat = { + x: prx.value, + y: pry.value, + z: prz.value, + w: prw.value, + }; + + const dampIntensity = 0.1 * ts; + const dampenedPR = quatSlerp(pointerRot, IDENTITY, dampIntensity); + prx.value = dampenedPR.x; + pry.value = dampenedPR.y; + prz.value = dampenedPR.z; + prw.value = dampenedPR.w; + + let snapRot: Quat = IDENTITY; + + if (!isDown.value) { + const snapDir: Vec3 = { x: 0, y: 0, z: -1 }; + const invOrientation = quatConjugate(orientation); + const transformedSnapDir = quatRotateVec3(invOrientation, snapDir); + + let maxDot = -Infinity; + let nearestIdx = 0; + + for (let i = 0; i < verticesRef.length; i++) { + const v = verticesRef[i]; + const dot = + transformedSnapDir.x * v.x + + transformedSnapDir.y * v.y + + transformedSnapDir.z * v.z; + if (dot > maxDot) { + maxDot = dot; + nearestIdx = i; + } + } + + const nearestV = verticesRef[nearestIdx]; + const worldV = quatRotateVec3(orientation, nearestV); + const targetDir = vec3Normalize(worldV); + + const sqrDist = + (targetDir.x - snapDir.x) ** 2 + + (targetDir.y - snapDir.y) ** 2 + + (targetDir.z - snapDir.z) ** 2; + const distFactor = Math.max(0.1, 1 - sqrDist * 10); + const snapIntensity = 0.2 * ts * distFactor; + snapRot = quatFromVectors(targetDir, snapDir, snapIntensity); + + const itemLen = Math.max(1, items.length); + const itemIdx = nearestIdx % itemLen; + if (activeIdx.value !== itemIdx) { + activeIdx.value = itemIdx; + scheduleOnRN(updateActiveItem, itemIdx); + } + } + + const combined = quatMultiply(snapRot, dampenedPR); + const newOrientation = quatNormalize(quatMultiply(combined, orientation)); + qx.value = newOrientation.x; + qy.value = newOrientation.y; + qz.value = newOrientation.z; + qw.value = newOrientation.w; + + const rad = Math.acos(Math.min(1, Math.max(-1, combined.w))) * 2; + const rv = rad / (2 * Math.PI); + rotVelocity.value += (rv - rotVelocity.value) * 0.5 * ts; + + const targetZ = isDown.value + ? CAMERA_Z + rotVelocity.value * 80 + 2.5 + : CAMERA_Z; + const damping = isDown.value ? 7 / ts : 5 / ts; + camZ.value += (targetZ - camZ.value) / damping; + + const moving = isDown.value || Math.abs(rotVelocity.value) > 0.005; + if (moving !== lastMoving.value) { + lastMoving.value = moving; + scheduleOnRN(updateIsMoving, moving); + } + + if (!moving && !isDown.value && Math.abs(rotVelocity.value) < 0.001) { + frameSkip.value++; + if (frameSkip.value > 5) { + return; + } + } else { + frameSkip.value = 0; + } + + const discs: IDisc[] = []; + const currentCamZ = camZ.value; + const itemLen = Math.max(1, items.length); + + for (let i = 0; i < verticesRef.length; i++) { + const v = verticesRef[i]; + const worldPos = quatRotateVec3(newOrientation, v); + + const perspective = currentCamZ / (currentCamZ - worldPos.z); + const sx = centerX + worldPos.x * perspective * PROJECTION_SCALE; + const sy = centerY - worldPos.y * perspective * PROJECTION_SCALE; + + const zFactor = (Math.abs(worldPos.z) / SPHERE_RADIUS) * 0.6 + 0.4; + const baseRadius = + zFactor * DISC_BASE_SCALE * perspective * PROJECTION_SCALE; + + const alpha = Math.max(0.1, (worldPos.z / SPHERE_RADIUS) * 0.45 + 0.55); + + discs.push({ + screenX: sx, + screenY: sy, + radius: baseRadius, + alpha: alpha, + z: worldPos.z, + itemIndex: i % itemLen, + }); + } + discs.sort((a, b) => a.z - b.z); + scheduleOnRN(updateDiscData, discs); + }); + + const panGesture = Gesture.Pan() + .onBegin((e) => { + "worklet"; + prevX.value = e.x; + prevY.value = e.y; + isDown.value = true; + }) + .onUpdate((e) => { + "worklet"; + const intensity = 0.3; + const amplification = 5; + + const midX = prevX.value + (e.x - prevX.value) * intensity; + const midY = prevY.value + (e.y - prevY.value) * intensity; + + const dx = midX - prevX.value; + const dy = midY - prevY.value; + + if (dx * dx + dy * dy > 0.1) { + const p = projectToSphere(midX, midY); + const q = projectToSphere(prevX.value, prevY.value); + const newRot = quatFromVectors(p, q, amplification); + + prx.value = newRot.x; + pry.value = newRot.y; + prz.value = newRot.z; + prw.value = newRot.w; + + prevX.value = midX; + prevY.value = midY; + } + }) + .onEnd(() => { + "worklet"; + isDown.value = false; + }) + .onFinalize(() => { + "worklet"; + isDown.value = false; + }); + + const fadeAnim = useMemo( + () => new RNAnimated.Value(1), + [], + ); + const scaleAnim = useMemo( + () => new RNAnimated.Value(1), + [], + ); + + useEffect(() => { + RNAnimated.parallel([ + RNAnimated.timing(fadeAnim, { + toValue: isMoving ? 0 : 1, + duration: isMoving ? 100 : 500, + useNativeDriver: true, + }), + RNAnimated.timing(scaleAnim, { + toValue: isMoving ? 0 : 1, + duration: isMoving ? 100 : 500, + useNativeDriver: true, + }), + ]).start(); + }, [isMoving, fadeAnim, scaleAnim]); + + return ( + + + + + {discData.map((disc, idx) => ( + + ))} + + + + + ); + }, +); + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + canvas: { + flex: 1, + }, +}); + +export default memo< + React.FC & React.FunctionComponent +>(InfiniteMenu); + +export type { IMenuItem, IInfiniteMenu, IDisc, IDiscComponent }; \ No newline at end of file diff --git a/src/components/ui/testimonials-card.tsx b/src/components/ui/testimonials-card.tsx new file mode 100644 index 0000000..68692bb --- /dev/null +++ b/src/components/ui/testimonials-card.tsx @@ -0,0 +1,153 @@ +import React, { useState, useMemo } from "react"; +import { motion, AnimatePresence } from "framer-motion"; +import { cn } from "../../lib/utils"; +import { ArrowLeft, ArrowRight } from "lucide-react"; + + +/** + * @typedef {Object} TestimonialItem + * @property {string|number} id + * @property {string} title + * @property {string} description + * @property {string} image + */ + +/** + * @typedef {Object} TestimonialsCardProps + * @property {TestimonialItem[]} items + * @property {string} [className] + * @property {number} [width=400] + * @property {boolean} [showNavigation=true] + * @property {boolean} [showCounter=true] + * @property {boolean} [autoPlay=false] + * @property {number} [autoPlayInterval=3000] + */ + +export function TestimonialsCard({ +items, +className, +width = 400, +showNavigation = true, +showCounter = true, +autoPlay = false, +autoPlayInterval = 3000, +}) { +const [activeIndex, setActiveIndex] = useState(0); +const [direction, setDirection] = useState(1); +const activeItem = items[activeIndex]; + +React.useEffect(() => { + if (!autoPlay || items.length <= 1) return; + const interval = setInterval(() => { + setDirection(1); + setActiveIndex((prev) => (prev + 1) % items.length); + }, autoPlayInterval); + return () => clearInterval(interval); +}, [autoPlay, autoPlayInterval, items.length]); + +const handleNext = () => { + if (activeIndex < items.length - 1) { + setDirection(1); + setActiveIndex(activeIndex + 1); + } +}; + +const handlePrev = () => { + if (activeIndex > 0) { + setDirection(-1); + setActiveIndex(activeIndex - 1); + } +}; + +const rotations = useMemo(() => [4, -2, -9, 7], []); + +if (!items || items.length === 0) return null; + +return ( +
+
+ {showCounter && ( +
+ {activeIndex + 1} / {items.length} +
+ )} + +
+ + {items.map((item, index) => { + const isActive = index === activeIndex; + const offset = index - activeIndex; + return ( + + {item.title} + + ); + })} + +
+ +
+ + +

{activeItem.title}

+

{activeItem.description}

+
+
+
+ + {showNavigation && items.length > 1 && ( +
+ + +
+ )} +
+
+); +} + +export default TestimonialsCard; \ No newline at end of file diff --git a/src/main.jsx b/src/main.jsx index e7ec7be..744fbfe 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -2,6 +2,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import { BrowserRouter } from "react-router-dom"; import App from "./App"; +import { Toaster } from 'react-hot-toast'; import './index.css' import 'lenis/dist/lenis.css'; import ScrollToTop from "./components/ScrollToTop"; @@ -11,6 +12,7 @@ ReactDOM.createRoot(document.getElementById("root")).render( + diff --git a/src/pages/AdminDashboard.jsx b/src/pages/AdminDashboard.jsx index ffab87c..4f97a9d 100644 --- a/src/pages/AdminDashboard.jsx +++ b/src/pages/AdminDashboard.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import { useNavigate } from 'react-router-dom'; import axios from 'axios'; +import { toast } from 'react-hot-toast'; import { FloatingNav } from '../components/FloatingNavbar'; import { IconHome, @@ -116,7 +117,7 @@ export default function AdminDashboard() { await axios.delete(`${API_URL}/admin/users/${id}`, getAuthHeader()); fetchData(); } catch (error) { - alert(error.response?.data?.message || 'Failed to delete user'); + toast.error(error.response?.data?.message || 'Failed to delete user'); } }; @@ -127,7 +128,7 @@ export default function AdminDashboard() { await axios.delete(`${API_URL}/events/${id}`, getAuthHeader()); fetchData(); } catch (error) { - alert('Failed to delete event'); + toast.error('Failed to delete event'); } }; @@ -136,7 +137,7 @@ export default function AdminDashboard() { await axios.put(`${API_URL}/admin/registrations/${id}/status`, { status }, getAuthHeader()); fetchData(); } catch (error) { - alert('Failed to update status'); + toast.error('Failed to update status'); } }; @@ -147,7 +148,7 @@ export default function AdminDashboard() { await axios.delete(`${API_URL}/admin/registrations/${id}`, getAuthHeader()); fetchData(); } catch (error) { - alert('Failed to delete registration'); + toast.error('Failed to delete registration'); } }; @@ -421,7 +422,7 @@ function CreateEventModal({ onClose }) { 'Content-Type': 'multipart/form-data' } }); - alert('Event created successfully!'); + toast.success('Event created successfully!'); onClose(); } catch (error) { setError(error.response?.data?.message || 'Failed to create event'); diff --git a/src/pages/ContactPage.jsx b/src/pages/ContactPage.jsx index 931307d..32c7bf9 100644 --- a/src/pages/ContactPage.jsx +++ b/src/pages/ContactPage.jsx @@ -175,44 +175,26 @@ export default function ContactPage() { } }; - const scrollToContact = () => { - document.getElementById("contact-section").scrollIntoView({ - behavior: "smooth", - }); - }; - return ( -
- +
+ -
-

+ {/* Single Scrollable Contact Section */} +
+ + {/* Heading */} +

Get In Touch

+ + {/* Subheading */} +

+ Have a question or want to work together? We'd love to hear from you. +

- -
- -
-

- Get In Touch -

- -

- Let's{" "} - Talk -

- -
+ {/* Contact Form */} +
@@ -242,8 +224,8 @@ export default function ContactPage() { value={formData.subject} onChange={handleChange} placeholder="Subject" - className="w-full px-4 sm:px-6 py-3 sm:py-4 bg-white border-2 border-gray-300 rounded-xl - focus:outline-none focus:border-black transition-all duration-300 text-black font-medium text-sm sm:text-base" + className="w-full px-4 sm:px-6 py-3 sm:py-4 bg-[#1a1a1a] border-2 border-gray-700 rounded-xl + focus:outline-none focus:border-gray-500 transition-all duration-300 text-white placeholder-gray-500 font-medium text-sm sm:text-base" />
{formData.message.length} / 5000 characters (minimum 10) @@ -266,12 +248,12 @@ export default function ContactPage() { className="w-full sm:w-52 group relative px-6 sm:px-8 py-3 sm:py-4 rounded-xl font-bold text-base sm:text-lg overflow-hidden transition-all duration-500 cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed" > - - + + - + {loading ? ( -
+
) : ( "Send Message" )} diff --git a/src/pages/EventPage.jsx b/src/pages/EventPage.jsx index 625d95e..cb0f9ed 100644 --- a/src/pages/EventPage.jsx +++ b/src/pages/EventPage.jsx @@ -18,37 +18,37 @@ const events = [ title: "Clash Of Coders 4.0", href: "/events/codeclash-2024", imageUrl: "https://codechefabesec.netlify.app/img/coc/1.webp", - hoverImageUrl: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWJjb3NqY21sYjY2aG1zZ3c3aGZ6Z3Z1c3NldWJrbjZ2eW54c3JqZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + hoverImageUrl: "/COC1.JPG", }, { title: "Rust - Ed", href: "/events/rust-ed", imageUrl: "https://codechefabesec.netlify.app/img/works/4/Rusted.webp", - hoverImageUrl: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWJjb3NqY21sYjY2aG1zZ3c3aGZ6Z3Z1c3NldWJrbjZ2eW54c3JqZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + hoverImageUrl: "/rust1.webp", }, { title: "Once Upon A Crime", href: "/events/once-upon-a-crime", imageUrl: "https://codechefabesec.netlify.app/img/converted/crime.webp", - hoverImageUrl: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWJjb3NqY21sYjY2aG1zZ3c3aGZ6Z3Z1c3NldWJrbjZ2eW54c3JqZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + hoverImageUrl: "/OUAC1.webp", }, { title: "T-Error 3.0", href: "/events/t-error-3", imageUrl: "https://codechefabesec.netlify.app/img/t_error/1.webp", - hoverImageUrl: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWJjb3NqY21sYjY2aG1zZ3c3aGZ6Z3Z1c3NldWJrbjZ2eW54c3JqZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + hoverImageUrl: "/TERROR1.webp", }, { title: "Beyond Code", href: "/events/byond-code", imageUrl: "https://codechefabesec.netlify.app/img/beyond_code/1.webp", - hoverImageUrl: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWJjb3NqY21sYjY2aG1zZ3c3aGZ6Z3Z1c3NldWJrbjZ2eW54c3JqZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + hoverImageUrl: "/bc1.webp", }, { title: "Head Node", href: "/events/head-node", imageUrl: "https://codechefabesec.netlify.app/img/converted/headnode.webp", - hoverImageUrl: "https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExbWJjb3NqY21sYjY2aG1zZ3c3aGZ6Z3Z1c3NldWJrbjZ2eW54c3JqZyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/13HgwGsXF0aiGY/giphy.gif", + hoverImageUrl: "/hn4.webp", }, ]; diff --git a/src/pages/EventRegistration.jsx b/src/pages/EventRegistration.jsx index 1b8d4ca..6a2eec7 100644 --- a/src/pages/EventRegistration.jsx +++ b/src/pages/EventRegistration.jsx @@ -1,6 +1,7 @@ import React, { useState, useEffect } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import axios from 'axios'; +import { toast } from 'react-hot-toast'; const API_URL = 'http://localhost:5000/api'; @@ -164,7 +165,7 @@ export default function EventRegistration() { } }); - alert('Registration submitted successfully! Await admin approval.'); + toast.success('Registration submitted successfully! Await admin approval.'); navigate('/'); } catch (err) { setError(err.response?.data?.message || 'Registration failed. Please try again.'); diff --git a/src/pages/HomePage.jsx b/src/pages/HomePage.jsx index 44feb28..131aab4 100644 --- a/src/pages/HomePage.jsx +++ b/src/pages/HomePage.jsx @@ -4,7 +4,10 @@ import ExPresident from "../components/ExPresident"; import Landing from "../components/Landing"; import Offering from "../components/Offering"; import About from "../components/about"; +import EventsGalary from "../components/eventsGalary"; +import AnimatedTestimonialsDemo from "../components/testimonial"; import useLenis from "../hooks/useLenis"; +import CompilationBar from "../components/requestAnimationFrame"; @@ -16,8 +19,12 @@ export default function HomePage() { + + + {/* */} {/*
*/} +
); } \ No newline at end of file diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx index 4842ce2..b54a0a4 100644 --- a/src/pages/Login.jsx +++ b/src/pages/Login.jsx @@ -6,7 +6,6 @@ const API_URL = 'http://localhost:5000/api'; export default function Login() { const navigate = useNavigate(); - const [loginType, setLoginType] = useState('user'); const [formData, setFormData] = useState({ email: '', password: '' @@ -30,19 +29,6 @@ export default function Login() { try { const response = await axios.post(`${API_URL}/auth/login`, formData); - // Check if role matches selected login type - if (loginType === 'admin' && response.data.user.role !== 'admin') { - setError('You are not authorized to login as admin'); - setLoading(false); - return; - } - - if (loginType === 'user' && response.data.user.role === 'admin') { - setError('Admin accounts must use admin login'); - setLoading(false); - return; - } - localStorage.setItem('token', response.data.token); localStorage.setItem('user', JSON.stringify(response.data.user)); @@ -77,37 +63,6 @@ export default function Login() {

Login to CodeChef ABESEC

- {/* Login Type Selection */} -
- -
- - -
-
- {error && (

{error}

@@ -126,10 +81,10 @@ export default function Login() { onChange={handleChange} required className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent transition-all text-gray-900" - placeholder={loginType === 'admin' ? 'admin@abes.ac.in' : 'your.email@abes.ac.in'} + placeholder="your.email@abes.ac.in" />

- {loginType === 'admin' ? 'Admin email required' : 'Only @abes.ac.in emails are allowed'} + Use your @abes.ac.in email address

@@ -153,20 +108,18 @@ export default function Login() { disabled={loading} className="w-full bg-blue-600 text-white py-3 rounded-lg font-semibold hover:bg-blue-700 focus:ring-4 focus:ring-blue-200 transition-all disabled:opacity-50 disabled:cursor-not-allowed" > - {loading ? 'Logging in...' : `Login as ${loginType === 'admin' ? 'Admin' : 'User'}`} + {loading ? 'Logging in...' : 'Login'} - {loginType === 'user' && ( -
-

- Don't have an account?{' '} - - Register here - -

-
- )} +
+

+ Don't have an account?{' '} + + Register here + +

+
diff --git a/src/pages/TeamPage.jsx b/src/pages/TeamPage.jsx index 64d39da..12c926d 100644 --- a/src/pages/TeamPage.jsx +++ b/src/pages/TeamPage.jsx @@ -5,14 +5,14 @@ const TeamPage = () => { return (
-
+
diff --git a/src/styles/Landing.css b/src/styles/Landing.css index 4d533d2..b9a3b2b 100644 --- a/src/styles/Landing.css +++ b/src/styles/Landing.css @@ -5,7 +5,7 @@ } .landing-heading { - font-size: 2rem; + font-size: 3rem; line-height: 1.1; text-align: center; word-break: keep-all; @@ -55,7 +55,7 @@ @media (min-width: 768px) { .landing-heading { - font-size: 3.2rem; + font-size: 4.5rem; text-align: left; } .cube { @@ -66,7 +66,7 @@ @media (min-width: 1024px) { .landing-heading { - font-size: 4.4rem; + font-size: 6rem; } .cube { --size: 320px; @@ -77,7 +77,7 @@ @media (min-width: 1440px) { .landing-heading { - font-size: 5.6rem; + font-size: 7.5rem; } .cube { --size: 380px; @@ -103,6 +103,6 @@ } @media (min-width: 480px) { .landing-heading { - font-size: 2.6rem; + font-size: 3.5rem; } }