From e15b10ea93039204411a2f65527a70fb06cb97a1 Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Tue, 6 Jan 2026 16:25:40 +0530 Subject: [PATCH 01/16] Update README.md --- frontend/README.md | 62 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/frontend/README.md b/frontend/README.md index b056748..e9118bd 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,3 +1,63 @@ +# React Project Setup and Deployment Guide for Windows + +This guide provides step-by-step instructions for setting up a React project. + +## 1. Setting Up the React Project + +### Install Node.js and npm + +```shell +apt update && apt install nodejs npm -y +``` + +### Verify Installation + + +```shell +node -v +npm -v +``` + + +## 2. Install Dependencies + +To install the necessary dependencies for your project, run the following command: + +```shell +npm install +``` + +## 3. Build the React Application for Production + +Update backend URL in .env file + +```shell +vim .env + + VITE_API_URL = "http://:8080/api" +``` + +To build the React application for production, run: + +```shell +npm run build +``` + +This will create a dist/ directory in your project containing optimized, production-ready files. + +## 4. Deploy production-ready files on s3 or apache2 server + +```shell +apt install apache2 -y +systemctl start apache2 +cp -rf dist/* /var/www/html/ +``` + +You can access the application on http://localhost:80 + + +--- + # CLOUDBLITZ Student Management Frontend A modern, scalable React application built with industry best practices for managing student registrations and data. @@ -292,4 +352,4 @@ For support and questions: - [ ] Advanced analytics dashboard - [ ] Multi-language support - [ ] Dark mode theme -- [ ] Advanced user roles and permissions \ No newline at end of file +- [ ] Advanced user roles and permissions From fbc3c71494924b1176127fae2bbfad4647cb5785 Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Tue, 6 Jan 2026 16:27:05 +0530 Subject: [PATCH 02/16] Update README.md --- frontend/README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frontend/README.md b/frontend/README.md index e9118bd..1d133af 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -31,10 +31,13 @@ npm install Update backend URL in .env file +```bash +vim .env +``` ```shell -vim .env - - VITE_API_URL = "http://:8080/api" +VITE_API_URL=http://$BACKEND:8080/api +VITE_API_BASE_URL=http://$BACKEND:8080 +VITE_APP_TITLE=EasyCRUD Student Registration ``` To build the React application for production, run: From fd0f744b72a1221bf27663097dcd1b7a6c6e1768 Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Tue, 6 Jan 2026 16:29:18 +0530 Subject: [PATCH 03/16] Update Readme.md --- backend/Readme.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/backend/Readme.md b/backend/Readme.md index 1d69609..57812d0 100644 --- a/backend/Readme.md +++ b/backend/Readme.md @@ -40,13 +40,15 @@ mvn -version ### Update DB credentials in application.properties: -```shell +```bash vim backend/src/main/resources/application.properties - - server.port=8080 - spring.datasource.url=jdbc:mariadb://:3306/ - spring.datasource.username= - spring.datasource.password= +``` +```shell +spring.datasource.url=jdbc:mariadb://localhost:3306/student_db +spring.datasource.username=your_username +spring.datasource.password=your_password +spring.jpa.hibernate.ddl-auto=validate +spring.jpa.show-sql=true ``` ### Build springboot Application using maven @@ -70,4 +72,5 @@ http://localhost:8080 ### Step 5: Keep the Application Running -To keep the application running in the background, you can use nohup or a similar method. \ No newline at end of file + +To keep the application running in the background, you can use nohup or a similar method. From 1f96ea1888d03673d7cccafbddd2bab378dbe0cf Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Tue, 6 Jan 2026 16:30:04 +0530 Subject: [PATCH 04/16] Update Readme.md --- backend/Readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/Readme.md b/backend/Readme.md index 57812d0..1541b84 100644 --- a/backend/Readme.md +++ b/backend/Readme.md @@ -39,6 +39,9 @@ mvn -version ## Step 3: Build the Spring Boot Application ### Update DB credentials in application.properties: +``` +pwd +``` ```bash vim backend/src/main/resources/application.properties @@ -74,3 +77,4 @@ http://localhost:8080 To keep the application running in the background, you can use nohup or a similar method. + From e880c8c64940d370b753c3e44753478b00fdf2c7 Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Tue, 6 Jan 2026 16:33:34 +0530 Subject: [PATCH 05/16] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4f60d99..d92ff6e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +## set up db_instance with mariadb engion +## create 2 Ec2 instances +## crete Sequrity group +Screenshot 2026-01-06 at 4 31 52โ€ฏPM + + + +--- # EasyCRUD - Student Registration System A full-stack web application for student registration with a React frontend and Spring Boot backend. @@ -325,4 +333,4 @@ For issues and questions: 1. Check the troubleshooting section 2. Review the database setup guide 3. Check application logs for error messages -4. Verify all prerequisites are installed \ No newline at end of file +4. Verify all prerequisites are installed From 61aeb5228203e6d14af780cd6ae7a2b7a2f82f0c Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Tue, 6 Jan 2026 16:57:46 +0530 Subject: [PATCH 06/16] Update README.md --- README.md | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/README.md b/README.md index d92ff6e..8ccdcb4 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,65 @@ ## crete Sequrity group Screenshot 2026-01-06 at 4 31 52โ€ฏPM +--- +# MariaDB Setup and Configuration Guide for Windows + +This guide explains how to set up MariaDB, create a database, and Create Database User + +## 1. Installing MariaDB + +Installing MariaDB on Ubntu + +```shell +apt update && apt install mariadb-server -y +``` + +## 2. Securing MariaDB + +Open the Command Prompt as Administrator and run the following command to secure your installation: + +```shell + +mysql_secure_installation +``` + +Follow the prompts to: +Set a root password. +Remove insecure default users and test databases. +Disable remote root login. + +## 3. Setting Up the Database + +Open terminal and login to MariaDB: + +```bash + +mysql -u root -p +``` + +Enter the root password when prompted. + +Create a new database and user: + +```sql +CREATE DATABASE student_db; +GRANT ALL PRIVILEGES ON springbackend.* TO 'username'@'localhost' IDENTIFIED BY 'your_password'; +``` +Replace username and your_password with your desired username and password. + +Exit MariaDB: + +```sql + +EXIT; +``` + +## 4. You will need Database Credentials to Connect Backend with Database +1. DB_HOST +2. DB_USER +3. DB_PASS +4. DB_PORT +5. DB_NAME --- From 24c5ac6b7d2fe2290b7806461136fc3090774b67 Mon Sep 17 00:00:00 2001 From: mukundDeo9325 Date: Wed, 11 Feb 2026 18:57:31 +0530 Subject: [PATCH 07/16] repo updtaed --- README.md | 453 ++--------- backend/.gitignore | 337 +------- backend/DATABASE_SETUP.md | 207 ----- backend/Dockerfile | 54 +- backend/Readme.md | 21 +- backend/backend-pod.yaml | 12 + backend/backend-svc.yaml | 11 + backend/database_schema.sql | 148 ---- backend/mvnw | 0 backend/pom.xml | 117 +-- backend/simple_schema.sql | 40 - .../config/WebConfig.java | 36 +- .../controller/HealthController.java | 19 - .../controller/UserController.java | 23 +- .../model/User.java | 7 + .../repository/UserRepository.java | 9 + .../src/main/resources/application.properties | 9 +- compose.yml | 20 + frontend/.env | 4 +- frontend/.gitignore | 140 +--- frontend/Dockerfile | 86 +- frontend/README.md | 414 ++-------- frontend/frontend-deploy.yml | 22 + frontend/frontend-svc.yml | 11 + frontend/index.html | 26 +- frontend/nginx-simple.conf | 55 -- frontend/nginx.conf | 58 -- frontend/package-lock.json | 93 +-- frontend/package.json | 68 +- frontend/src/App.jsx | 93 +-- frontend/src/api/userService.js | 64 +- .../src/components/common/ErrorBoundary.css | 191 ----- .../src/components/common/ErrorBoundary.jsx | 82 -- frontend/src/components/layout/Layout.css | 225 ------ frontend/src/components/layout/Layout.jsx | 66 -- frontend/src/context/AppContext.jsx | 148 ---- frontend/src/index.css | 735 ------------------ frontend/src/main.jsx | 22 +- frontend/src/pages/Dashboard.css | 387 --------- frontend/src/pages/Dashboard.jsx | 187 ----- frontend/src/pages/NotFound.css | 218 ------ frontend/src/pages/NotFound.jsx | 49 -- frontend/src/pages/StudentList.css | 576 -------------- frontend/src/pages/StudentList.jsx | 360 --------- frontend/src/pages/StudentRegistration.css | 299 ------- frontend/src/pages/StudentRegistration.jsx | 343 -------- frontend/src/utils/config.js | 20 - frontend/vite.config.js | 14 +- setup/docker/DOCKER_SETUP.md | 353 --------- setup/docker/docker-compose.dev.yml | 83 -- setup/docker/docker-compose.yml | 83 -- setup/docker/env.example | 82 -- setup/docker/nginx/nginx.conf | 133 ---- 53 files changed, 461 insertions(+), 6852 deletions(-) delete mode 100644 backend/DATABASE_SETUP.md create mode 100644 backend/backend-pod.yaml create mode 100644 backend/backend-svc.yaml delete mode 100644 backend/database_schema.sql mode change 100755 => 100644 backend/mvnw delete mode 100644 backend/simple_schema.sql delete mode 100644 backend/src/main/java/com/student/registration/student_registration_backend/controller/HealthController.java create mode 100644 backend/src/main/java/com/student/registration/student_registration_backend/repository/UserRepository.java create mode 100644 compose.yml create mode 100644 frontend/frontend-deploy.yml create mode 100644 frontend/frontend-svc.yml delete mode 100644 frontend/nginx-simple.conf delete mode 100644 frontend/nginx.conf delete mode 100644 frontend/src/components/common/ErrorBoundary.css delete mode 100644 frontend/src/components/common/ErrorBoundary.jsx delete mode 100644 frontend/src/components/layout/Layout.css delete mode 100644 frontend/src/components/layout/Layout.jsx delete mode 100644 frontend/src/context/AppContext.jsx delete mode 100644 frontend/src/index.css delete mode 100644 frontend/src/pages/Dashboard.css delete mode 100644 frontend/src/pages/Dashboard.jsx delete mode 100644 frontend/src/pages/NotFound.css delete mode 100644 frontend/src/pages/NotFound.jsx delete mode 100644 frontend/src/pages/StudentList.css delete mode 100644 frontend/src/pages/StudentList.jsx delete mode 100644 frontend/src/pages/StudentRegistration.css delete mode 100644 frontend/src/pages/StudentRegistration.jsx delete mode 100644 frontend/src/utils/config.js delete mode 100644 setup/docker/DOCKER_SETUP.md delete mode 100644 setup/docker/docker-compose.dev.yml delete mode 100644 setup/docker/docker-compose.yml delete mode 100644 setup/docker/env.example delete mode 100644 setup/docker/nginx/nginx.conf diff --git a/README.md b/README.md index 8ccdcb4..fcf4679 100644 --- a/README.md +++ b/README.md @@ -1,395 +1,58 @@ -## set up db_instance with mariadb engion -## create 2 Ec2 instances -## crete Sequrity group -Screenshot 2026-01-06 at 4 31 52โ€ฏPM - ---- -# MariaDB Setup and Configuration Guide for Windows - -This guide explains how to set up MariaDB, create a database, and Create Database User - -## 1. Installing MariaDB - -Installing MariaDB on Ubntu - -```shell -apt update && apt install mariadb-server -y -``` - -## 2. Securing MariaDB - -Open the Command Prompt as Administrator and run the following command to secure your installation: - -```shell - -mysql_secure_installation -``` - -Follow the prompts to: -Set a root password. -Remove insecure default users and test databases. -Disable remote root login. - -## 3. Setting Up the Database - -Open terminal and login to MariaDB: - -```bash - -mysql -u root -p -``` - -Enter the root password when prompted. - -Create a new database and user: - -```sql -CREATE DATABASE student_db; -GRANT ALL PRIVILEGES ON springbackend.* TO 'username'@'localhost' IDENTIFIED BY 'your_password'; -``` -Replace username and your_password with your desired username and password. - -Exit MariaDB: - -```sql - -EXIT; -``` - -## 4. You will need Database Credentials to Connect Backend with Database -1. DB_HOST -2. DB_USER -3. DB_PASS -4. DB_PORT -5. DB_NAME - - ---- -# EasyCRUD - Student Registration System - -A full-stack web application for student registration with a React frontend and Spring Boot backend. - -## ๐Ÿš€ Current Status - -The application is currently configured to run **without database dependencies** for easy development and testing. All data is stored in memory and will be reset when the application restarts. - -## ๐Ÿ“ Project Structure - -``` -EasyCRUD/ -โ”œโ”€โ”€ backend/ # Spring Boot Backend -โ”‚ โ”œโ”€โ”€ src/main/java/ -โ”‚ โ”‚ โ””โ”€โ”€ com/student/registration/ -โ”‚ โ”‚ โ””โ”€โ”€ student_registration_backend/ -โ”‚ โ”‚ โ”œโ”€โ”€ controller/ # REST Controllers -โ”‚ โ”‚ โ”œโ”€โ”€ model/ # Data Models -โ”‚ โ”‚ โ””โ”€โ”€ config/ # Configuration -โ”‚ โ”œโ”€โ”€ src/main/resources/ -โ”‚ โ”‚ โ””โ”€โ”€ application.properties # App Configuration -โ”‚ โ”œโ”€โ”€ database_schema.sql # Complete Database Schema -โ”‚ โ”œโ”€โ”€ simple_schema.sql # Minimal Database Schema -โ”‚ โ”œโ”€โ”€ DATABASE_SETUP.md # Database Setup Guide -โ”‚ โ””โ”€โ”€ pom.xml # Maven Dependencies -โ””โ”€โ”€ frontend/ # React Frontend - โ”œโ”€โ”€ src/ - โ”‚ โ”œโ”€โ”€ components/ # React Components - โ”‚ โ”œโ”€โ”€ api/ # API Service - โ”‚ โ””โ”€โ”€ hooks/ # Custom Hooks - โ””โ”€โ”€ package.json # Node Dependencies -``` - -## ๐Ÿ› ๏ธ Quick Start - -### Prerequisites -- Java 17 or higher -- Node.js 16 or higher -- Maven (or use the included Maven wrapper) - -### Backend Setup - -1. **Navigate to backend directory:** - ```bash - cd backend - ``` - -2. **Build the application:** - ```bash - # Make Maven wrapper executable (first time only) - chmod +x mvnw - - # Build the project - ./mvnw clean package - ``` - -3. **Run the application:** - ```bash - # Using Maven - ./mvnw spring-boot:run - - # Or using the JAR file - java -jar target/student-registration-backend-0.0.1-SNAPSHOT.jar - ``` - -The backend will start on `http://localhost:8080` - -### Frontend Setup - -1. **Navigate to frontend directory:** - ```bash - cd frontend - ``` - -2. **Install dependencies:** - ```bash - npm install - ``` - -3. **Start the development server:** - ```bash - npm run dev - ``` - -The frontend will start on `http://localhost:5173` - -## ๐Ÿ“Š API Endpoints - -### Student Registration API - -| Method | Endpoint | Description | -|--------|----------|-------------| -| POST | `/api/register` | Register a new student | -| GET | `/api/users` | Get all students | -| DELETE | `/api/users/{id}` | Delete a student by ID | - -### Sample API Usage - -```bash -# Register a new student -curl -X POST http://localhost:8080/api/register \ - -H "Content-Type: application/json" \ - -d '{ - "name": "John Doe", - "email": "john@example.com", - "course": "Computer Science", - "studentClass": "Final Year", - "percentage": 85.5, - "branch": "Computer Engineering", - "mobileNumber": "+1234567890" - }' - -# Get all students -curl http://localhost:8080/api/users - -# Delete a student -curl -X DELETE http://localhost:8080/api/users/1 -``` - -## ๐Ÿ—„๏ธ Database Integration (Optional) - -### Current State -- โœ… **No database dependencies** - Application runs independently -- โœ… **In-memory storage** - Data persists during runtime -- โœ… **Ready for database integration** - Schema files provided - -### Database Schema Files - -The project includes comprehensive database schemas: - -1. **`database_schema.sql`** - Complete schema with: - - Users table with all fields - - Courses and branches tables - - User roles and permissions - - Audit logging - - Sample data (10 students, 6 courses, 5 branches) - -2. **`simple_schema.sql`** - Minimal schema with: - - Basic users table - - Sample data (5 students) - -3. **`DATABASE_SETUP.md`** - Complete setup guide - -### MariaDB Setup (Ubuntu) - -1. **Install MariaDB:** - ```bash - sudo apt update && sudo apt install mariadb-server -y - ``` - -2. **Secure the installation:** - ```bash - sudo mysql_secure_installation - ``` - -3. **Create database and user:** - ```bash - sudo mysql -u root -p - ``` - ```sql - CREATE DATABASE student_db; - GRANT ALL PRIVILEGES ON student_db.* TO 'username'@'localhost' IDENTIFIED BY 'your_password'; - FLUSH PRIVILEGES; - EXIT; - ``` - -4. **Import the schema:** - ```bash - mysql -u username -p student_db < database_schema.sql - ``` - -### Adding Database Support - -When ready to add database support: - -1. **Add dependencies to `pom.xml`:** - ```xml - - org.springframework.boot - spring-boot-starter-data-jpa - - - org.mariadb.jdbc - mariadb-java-client - runtime - - ``` - -2. **Update `application.properties`:** - ```properties - spring.datasource.url=jdbc:mariadb://localhost:3306/student_db - spring.datasource.username=your_username - spring.datasource.password=your_password - spring.jpa.hibernate.ddl-auto=validate - spring.jpa.show-sql=true - ``` - -3. **Restore JPA annotations in User.java:** - ```java - @Entity - @Data - public class User { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - // ... other fields - } - ``` - -4. **Recreate UserRepository.java:** - ```java - @Repository - public interface UserRepository extends JpaRepository { - } - ``` - -## ๐Ÿ”ง Configuration - -### Environment Variables - -When using database, set these environment variables: - -```bash -export DB_HOST=localhost -export DB_USER=your_username -export DB_PASS=your_password -export DB_PORT=3306 -export DB_NAME=student_db -``` - -### Application Properties - -Key configuration options in `application.properties`: - -```properties -# Server Configuration -server.port=8080 - -# Database Configuration (when enabled) -spring.datasource.url=jdbc:mariadb://localhost:3306/student_db -spring.datasource.username=${DB_USER:root} -spring.datasource.password=${DB_PASS:} -spring.jpa.hibernate.ddl-auto=validate -spring.jpa.show-sql=true -``` - -## ๐Ÿงช Testing - -### Backend Testing -```bash -cd backend -./mvnw test -``` - -### API Testing -```bash -# Test registration -curl -X POST http://localhost:8080/api/register \ - -H "Content-Type: application/json" \ - -d '{"name":"Test User","email":"test@example.com","course":"CS","studentClass":"First Year","percentage":85.0,"branch":"Computer Engineering","mobileNumber":"+1234567890"}' - -# Test retrieval -curl http://localhost:8080/api/users -``` - -## ๐Ÿ“ Features - -### Current Features -- โœ… Student registration form -- โœ… View all registered students -- โœ… Delete students -- โœ… Responsive UI design -- โœ… Form validation -- โœ… In-memory data storage - -### Planned Features (with Database) -- ๐Ÿ”„ Persistent data storage -- ๐Ÿ”„ User authentication -- ๐Ÿ”„ Role-based access control -- ๐Ÿ”„ Data export/import -- ๐Ÿ”„ Advanced search and filtering -- ๐Ÿ”„ Audit logging - -## ๐Ÿ› Troubleshooting - -### Common Issues - -1. **Maven build fails:** - - Ensure Java 17+ is installed - - Check if Maven wrapper is executable: `chmod +x mvnw` - -2. **Port already in use:** - - Change port in `application.properties`: `server.port=8081` - -3. **Frontend can't connect to backend:** - - Check CORS configuration in `WebConfig.java` - - Verify backend is running on correct port - -4. **Database connection issues:** - - Verify MariaDB service is running: `sudo systemctl status mariadb` - - Check credentials and permissions - - Ensure database exists: `SHOW DATABASES;` - -## ๐Ÿ“š Documentation - -- **`DATABASE_SETUP.md`** - Complete database setup guide -- **`database_schema.sql`** - Full database schema with sample data -- **`simple_schema.sql`** - Minimal database schema for quick setup - -## ๐Ÿค Contributing - -1. Fork the repository -2. Create a feature branch -3. Make your changes -4. Test thoroughly -5. Submit a pull request - -## ๐Ÿ“„ License - -This project is open source and available under the [MIT License](LICENSE). - -## ๐Ÿ†˜ Support - -For issues and questions: -1. Check the troubleshooting section -2. Review the database setup guide -3. Check application logs for error messages -4. Verify all prerequisites are installed +# MariaDB Setup and Configuration Guide for Windows + +This guide explains how to set up MariaDB, create a database, and Create Database User + +## 1. Installing MariaDB + +Installing MariaDB on Ubntu + +```shell +apt update && apt install mariadb-server -y +``` + +## 2. Securing MariaDB + +Open the Command Prompt as Administrator and run the following command to secure your installation: + +```shell + +mysql_secure_installation +``` + +Follow the prompts to: +Set a root password. +Remove insecure default users and test databases. +Disable remote root login. + +## 3. Setting Up the Database + +Open terminal and login to MariaDB: + +```bash + +mysql -u root -p +``` + +Enter the root password when prompted. + +Create a new database and user: + +```sql +CREATE DATABASE student_db; +GRANT ALL PRIVILEGES ON springbackend.* TO 'username'@'localhost' IDENTIFIED BY 'your_password'; +``` +Replace username and your_password with your desired username and password. + +Exit MariaDB: + +```sql + +EXIT; +``` + +## 4. You will need Database Credentials to Connect Backend with Database +1. DB_HOST +2. DB_USER +3. DB_PASS +4. DB_PORT +5. DB_NAME \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore index 75ee0c5..7bc4bf7 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,58 +1,10 @@ -# Compiled class files -*.class - -# Log files -*.log -logs/ - -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files -*.jar -*.war -*.nar -*.ear -*.zip -*.tar.gz -*.rar - -# Virtual machine crash logs -hs_err_pid* -replay_pid* - -# Maven +HELP.md target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -.mvn/wrapper/maven-wrapper.jar - -# Gradle -.gradle/ -build/ -!gradle/wrapper/gradle-wrapper.jar -!**/src/main/**/build/ -!**/src/test/**/build/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ -# IntelliJ IDEA -.idea/ -*.iws -*.iml -*.ipr -out/ -!**/src/main/**/out/ -!**/src/test/**/out/ - -# Eclipse +### STS ### .apt_generated .classpath .factorypath @@ -60,279 +12,22 @@ out/ .settings .springBeans .sts4-cache -bin/ -!**/src/main/**/bin/ -!**/src/test/**/bin/ -# NetBeans +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ - -# VS Code -.vscode/ - -# Mac -.DS_Store - -# Windows -Thumbs.db -ehthumbs.db -Desktop.ini - -# Linux -*~ - -# Application specific -application-local.properties -application-dev.properties -application-prod.properties -application-test.properties - -# Environment variables -.env -.env.local -.env.development -.env.test -.env.production - -# Database -*.db -*.sqlite -*.sqlite3 - -# Temporary files -*.tmp -*.temp -temp/ -tmp/ - -# Backup files -*.bak -*.backup -*.old - -# OS generated files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Spring Boot specific -spring.log -application.log - -# H2 Database -*.h2.db -*.h2.db.trace - -# Liquibase -dbchangelog.sql - -# Flyway -flyway.sql - -# Test reports -test-output/ -reports/ -coverage/ - -# Performance monitoring -*.hprof - -# Docker -.dockerignore -docker-compose.override.yml - -# Kubernetes -*.yaml.bak -*.yml.bak - -# Local configuration -config/local/ -config/dev/ -config/test/ - -# SSL certificates -*.p12 -*.pem -*.key -*.crt -*.csr - -# JWT tokens -jwt-secret.txt -jwt-public.key -jwt-private.key - -# API documentation -swagger-ui/ -api-docs/ - -# Monitoring -prometheus.yml -grafana/ - -# Log aggregation -logstash/ -elasticsearch/ - -# Cache -.cache/ -cache/ - -# Profiling -*.prof -*.jfr - -# Memory dumps -*.hprof -*.dump - -# IDE specific -.project -.classpath -.settings/ -.metadata/ -*.launch - -# Build tools -ant/ -ant-build/ -build.xml - -# Package managers -package-lock.json -yarn.lock -pnpm-lock.yaml - -# Node.js (if using Node.js tools) -node_modules/ -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Python (if using Python tools) -__pycache__/ -*.py[cod] -*$py.class -*.so -.Python -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Ruby (if using Ruby tools) -*.gem -*.rbc -/.config -/coverage/ -/InstalledFiles -/pkg/ -/spec/reports/ -/spec/examples.txt -/test/tmp/ -/test/version_tmp/ -/tmp/ - -# Go (if using Go tools) -*.exe -*.exe~ -*.dll -*.so -*.dylib -*.test -*.out -go.work - -# Rust (if using Rust tools) -/target/ -Cargo.lock - -# PHP (if using PHP tools) -/vendor/ -composer.phar -composer.lock - -# .NET (if using .NET tools) -*.user -*.userosscache -*.suo -*.userprefs -*.dbmdl -*.dbproj.schemaview -*.jfm -*.pfx -*.publishsettings -orleans.codegen.cs - -# Scala (if using Scala tools) -*.class -*.log -.cache -.history -.repl_history -project/ -target/ -lib_managed/ -src_managed/ -project/boot/ -project/plugins/project/ - -# Clojure (if using Clojure tools) -.cpcache/ -.lein-* -.nrepl-port -.strepl-port -*.jar -*.class - -# Leiningen -target/ -classes/ -checkouts/ -pom.xml -pom.xml.asc -*.jar -*.class - -# Boot -.boot/ - -# Profiles -profiles.clj - -# Local configuration -local.clj - -# Clojure REPL -.nrepl-port -.strepl-port - -# ClojureScript -.cache/ -.parcel-cache/ -dist/ build/ +!**/src/main/**/build/ +!**/src/test/**/build/ -# ClojureScript REPL -.rebel_readline_history - -# ClojureScript DevTools -.cljsbuild/ - -# ClojureScript Karma -karma.conf.js - -# ClojureScript Karma -karma.conf.js - -# ClojureScript Karma -karma.conf.js +### VS Code ### +.vscode/ diff --git a/backend/DATABASE_SETUP.md b/backend/DATABASE_SETUP.md deleted file mode 100644 index 6fd9016..0000000 --- a/backend/DATABASE_SETUP.md +++ /dev/null @@ -1,207 +0,0 @@ -# Database Setup Guide - -This guide explains how to set up the database for the Student Registration System. - -## Database Schema Overview - -The schema includes the following tables: - -### Core Tables -- **users** - Main table for student information -- **courses** - Available courses/programs -- **branches** - Different engineering branches -- **user_roles** - Role definitions (STUDENT, ADMIN, FACULTY, STAFF) -- **user_role_mapping** - Many-to-many relationship between users and roles -- **audit_logs** - Track changes to data for audit purposes - -## Setup Instructions - -### 1. Choose Your Database System - -The schema is compatible with: -- **MySQL** (5.7+) -- **MariaDB** (10.2+) -- **PostgreSQL** (with minor modifications) - -### 2. Create Database - -#### For MySQL/MariaDB: -```sql -CREATE DATABASE student_db; -USE student_db; -``` - -#### For PostgreSQL: -```sql -CREATE DATABASE student_db; -\c student_db; -``` - -### 3. Import Schema - -#### Option A: Using Command Line -```bash -# MySQL -mysql -u your_username -p student_db < database_schema.sql - -# MariaDB -mariadb -u your_username -p student_db < database_schema.sql - -# PostgreSQL -psql -U your_username -d student_db -f database_schema.sql -``` - -#### Option B: Using Database GUI Tools -- **MySQL Workbench**: File โ†’ Open SQL Script โ†’ Select `database_schema.sql` -- **phpMyAdmin**: Import โ†’ Choose File โ†’ Select `database_schema.sql` -- **pgAdmin**: Tools โ†’ Query Tool โ†’ Load file โ†’ Select `database_schema.sql` - -### 4. Verify Setup - -After importing, you should see: -- 6 tables created -- 10 sample users inserted -- 6 courses inserted -- 5 branches inserted -- 4 user roles inserted -- Sample data relationships established - -## Database Configuration - -### For Spring Boot Application - -When you're ready to add database support back to your Spring Boot application, update your `application.properties`: - -```properties -# Database Configuration -spring.datasource.url=jdbc:mysql://localhost:3306/student_db -spring.datasource.username=your_username -spring.datasource.password=your_password -spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver - -# JPA Configuration -spring.jpa.hibernate.ddl-auto=validate -spring.jpa.show-sql=true -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect -spring.jpa.properties.hibernate.format_sql=true - -# Connection Pool -spring.datasource.hikari.maximum-pool-size=10 -spring.datasource.hikari.minimum-idle=5 -spring.datasource.hikari.idle-timeout=300000 -``` - -### For PostgreSQL: -```properties -spring.datasource.url=jdbc:postgresql://localhost:5432/student_db -spring.datasource.username=your_username -spring.datasource.password=your_password -spring.datasource.driver-class-name=org.postgresql.Driver -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect -``` - -## Table Structure Details - -### Users Table -```sql -CREATE TABLE users ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - email VARCHAR(255) NOT NULL UNIQUE, - course VARCHAR(255), - student_class VARCHAR(100), - percentage DECIMAL(5,2), - branch VARCHAR(255), - mobile_number VARCHAR(20), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); -``` - -### Key Features: -- **Auto-incrementing ID**: Primary key -- **Email uniqueness**: Prevents duplicate registrations -- **Audit fields**: `created_at` and `updated_at` timestamps -- **Indexes**: Optimized for email, course, and branch queries - -## Sample Data - -The schema includes 10 sample students with realistic data: -- Various engineering courses (CS, EE, ME, CE, IT, ECE) -- Different academic years (First Year to Final Year) -- Realistic percentage scores (76-92%) -- Different branches and mobile numbers - -## Security Considerations - -### 1. User Authentication -- Implement password hashing (BCrypt recommended) -- Add password field to users table -- Use Spring Security for authentication - -### 2. Role-Based Access Control -- Use the `user_roles` and `user_role_mapping` tables -- Implement @PreAuthorize annotations in controllers -- Create different access levels for students, faculty, and admins - -### 3. Data Validation -- Add constraints for email format -- Validate percentage range (0-100) -- Ensure mobile number format - -## Backup and Maintenance - -### Create Backup -```bash -# MySQL -mysqldump -u username -p student_db > backup_$(date +%Y%m%d_%H%M%S).sql - -# PostgreSQL -pg_dump -U username student_db > backup_$(date +%Y%m%d_%H%M%S).sql -``` - -### Restore Backup -```bash -# MySQL -mysql -u username -p student_db < backup_file.sql - -# PostgreSQL -psql -U username -d student_db < backup_file.sql -``` - -## Troubleshooting - -### Common Issues: - -1. **Connection Refused** - - Check if database service is running - - Verify port numbers (MySQL: 3306, PostgreSQL: 5432) - -2. **Access Denied** - - Verify username and password - - Check user permissions on database - -3. **Table Already Exists** - - The schema includes `DROP TABLE IF EXISTS` statements - - This will safely remove existing tables - -4. **Character Encoding Issues** - - Ensure database uses UTF-8 encoding - - Add `?useUnicode=true&characterEncoding=UTF-8` to JDBC URL - -## Next Steps - -1. **Import the schema** using the instructions above -2. **Test the database** with sample queries -3. **Update your Spring Boot application** to use database instead of in-memory storage -4. **Add authentication and authorization** using Spring Security -5. **Implement data validation** and error handling -6. **Set up regular backups** for production use - -## Support - -If you encounter any issues: -1. Check the database logs for error messages -2. Verify your database version compatibility -3. Ensure all required permissions are granted -4. Test with a simple connection first \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index 898202e..70f0a37 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,46 +1,10 @@ -# Multi-stage build for Spring Boot application -FROM maven:3.9.6-eclipse-temurin-17 AS build - -# Set working directory -WORKDIR /app - -# Copy pom.xml and download dependencies -COPY pom.xml . -RUN mvn dependency:go-offline -B - -# Copy source code -COPY src ./src - -# Build the application -RUN mvn clean package -DskipTests - -# Runtime stage -FROM eclipse-temurin:17-jre - -# Install curl for health checks -RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* - -# Create app user -RUN groupadd -r appuser && useradd -r -g appuser appuser - -# Set working directory -WORKDIR /app - -# Copy the built JAR from build stage -COPY --from=build /app/target/student-registration-backend-0.0.1-SNAPSHOT.jar app.jar - -# Change ownership to app user -RUN chown appuser:appuser app.jar - -# Switch to app user -USER appuser - -# Expose port +FROM maven:3.8.3-openjdk-17 +COPY . /opt/ +WORKDIR /opt +RUN rm -f src/main/resources/application.properties && \ + cp -f application.properties src/main/resources/application.properties && \ + mvn clean package +WORKDIR target/ EXPOSE 8080 - -# Health check -HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ - CMD curl -f http://localhost:8080/health || exit 1 - -# Run the application -ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file +ENTRYPOINT ["java","-jar"] +CMD ["student-registration-backend-0.0.1-SNAPSHOT.jar"] diff --git a/backend/Readme.md b/backend/Readme.md index 1541b84..1d69609 100644 --- a/backend/Readme.md +++ b/backend/Readme.md @@ -39,19 +39,14 @@ mvn -version ## Step 3: Build the Spring Boot Application ### Update DB credentials in application.properties: -``` -pwd -``` -```bash -vim backend/src/main/resources/application.properties -``` ```shell -spring.datasource.url=jdbc:mariadb://localhost:3306/student_db -spring.datasource.username=your_username -spring.datasource.password=your_password -spring.jpa.hibernate.ddl-auto=validate -spring.jpa.show-sql=true +vim backend/src/main/resources/application.properties + + server.port=8080 + spring.datasource.url=jdbc:mariadb://:3306/ + spring.datasource.username= + spring.datasource.password= ``` ### Build springboot Application using maven @@ -75,6 +70,4 @@ http://localhost:8080 ### Step 5: Keep the Application Running - -To keep the application running in the background, you can use nohup or a similar method. - +To keep the application running in the background, you can use nohup or a similar method. \ No newline at end of file diff --git a/backend/backend-pod.yaml b/backend/backend-pod.yaml new file mode 100644 index 0000000..6c6fa6f --- /dev/null +++ b/backend/backend-pod.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Pod +metadata: + name: backend + labels: + app: backend +spec: + containers: + - name: backend + image: r123mahajan/backend:latest + ports: + - containerPort: 80 diff --git a/backend/backend-svc.yaml b/backend/backend-svc.yaml new file mode 100644 index 0000000..73f6b6a --- /dev/null +++ b/backend/backend-svc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: backend-svc +spec: + type: LoadBalancer + selector: + app: backend + ports: + - port: 8080 + targetPort: 8080 diff --git a/backend/database_schema.sql b/backend/database_schema.sql deleted file mode 100644 index 39e7a04..0000000 --- a/backend/database_schema.sql +++ /dev/null @@ -1,148 +0,0 @@ --- Database Schema for Student Registration System --- This file contains the complete database setup for the application - --- Create database (uncomment and modify as needed for your database system) --- CREATE DATABASE student_db; --- USE student_db; - --- Drop table if exists (for clean setup) -DROP TABLE IF EXISTS users; - --- Create users table -CREATE TABLE users ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - email VARCHAR(255) NOT NULL UNIQUE, - course VARCHAR(255), - student_class VARCHAR(100), - percentage DECIMAL(5,2), - branch VARCHAR(255), - mobile_number VARCHAR(20), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - --- Create indexes for better performance -CREATE INDEX idx_users_email ON users(email); -CREATE INDEX idx_users_course ON users(course); -CREATE INDEX idx_users_branch ON users(branch); - --- Insert sample data -INSERT INTO users (name, email, course, student_class, percentage, branch, mobile_number) VALUES -('John Doe', 'john.doe@example.com', 'Computer Science', 'Final Year', 85.50, 'Computer Engineering', '+1234567890'), -('Jane Smith', 'jane.smith@example.com', 'Electrical Engineering', 'Third Year', 78.25, 'Electrical Engineering', '+1234567891'), -('Mike Johnson', 'mike.johnson@example.com', 'Mechanical Engineering', 'Second Year', 82.75, 'Mechanical Engineering', '+1234567892'), -('Sarah Wilson', 'sarah.wilson@example.com', 'Computer Science', 'First Year', 90.00, 'Computer Engineering', '+1234567893'), -('David Brown', 'david.brown@example.com', 'Civil Engineering', 'Final Year', 76.80, 'Civil Engineering', '+1234567894'), -('Emily Davis', 'emily.davis@example.com', 'Information Technology', 'Third Year', 88.90, 'Computer Engineering', '+1234567895'), -('Robert Miller', 'robert.miller@example.com', 'Electronics Engineering', 'Second Year', 79.45, 'Electronics Engineering', '+1234567896'), -('Lisa Garcia', 'lisa.garcia@example.com', 'Computer Science', 'First Year', 92.30, 'Computer Engineering', '+1234567897'), -('James Rodriguez', 'james.rodriguez@example.com', 'Mechanical Engineering', 'Final Year', 81.20, 'Mechanical Engineering', '+1234567898'), -('Maria Martinez', 'maria.martinez@example.com', 'Electrical Engineering', 'Third Year', 87.60, 'Electrical Engineering', '+1234567899'); - --- Create additional tables for enhanced functionality (optional) - --- Create courses table -CREATE TABLE courses ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - course_name VARCHAR(255) NOT NULL UNIQUE, - course_code VARCHAR(50) NOT NULL UNIQUE, - duration_years INT DEFAULT 4, - description TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Create branches table -CREATE TABLE branches ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - branch_name VARCHAR(255) NOT NULL UNIQUE, - branch_code VARCHAR(50) NOT NULL UNIQUE, - description TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Insert sample courses -INSERT INTO courses (course_name, course_code, duration_years, description) VALUES -('Computer Science', 'CS', 4, 'Bachelor of Technology in Computer Science'), -('Electrical Engineering', 'EE', 4, 'Bachelor of Technology in Electrical Engineering'), -('Mechanical Engineering', 'ME', 4, 'Bachelor of Technology in Mechanical Engineering'), -('Civil Engineering', 'CE', 4, 'Bachelor of Technology in Civil Engineering'), -('Information Technology', 'IT', 4, 'Bachelor of Technology in Information Technology'), -('Electronics Engineering', 'ECE', 4, 'Bachelor of Technology in Electronics Engineering'); - --- Insert sample branches -INSERT INTO branches (branch_name, branch_code, description) VALUES -('Computer Engineering', 'CSE', 'Computer Science and Engineering'), -('Electrical Engineering', 'EEE', 'Electrical and Electronics Engineering'), -('Mechanical Engineering', 'MECH', 'Mechanical Engineering'), -('Civil Engineering', 'CIVIL', 'Civil Engineering'), -('Electronics Engineering', 'ECE', 'Electronics and Communication Engineering'); - --- Create user_roles table for role-based access (optional) -CREATE TABLE user_roles ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - role_name VARCHAR(50) NOT NULL UNIQUE, - description TEXT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Insert default roles -INSERT INTO user_roles (role_name, description) VALUES -('STUDENT', 'Regular student user'), -('ADMIN', 'Administrator with full access'), -('FACULTY', 'Faculty member'), -('STAFF', 'Staff member'); - --- Create user_role_mapping table (optional) -CREATE TABLE user_role_mapping ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - user_id BIGINT NOT NULL, - role_id BIGINT NOT NULL, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, - FOREIGN KEY (role_id) REFERENCES user_roles(id) ON DELETE CASCADE, - UNIQUE KEY unique_user_role (user_id, role_id) -); - --- Assign default role to existing users -INSERT INTO user_role_mapping (user_id, role_id) -SELECT u.id, r.id -FROM users u, user_roles r -WHERE r.role_name = 'STUDENT'; - --- Create audit log table for tracking changes (optional) -CREATE TABLE audit_logs ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - table_name VARCHAR(100) NOT NULL, - record_id BIGINT NOT NULL, - action VARCHAR(20) NOT NULL, -- INSERT, UPDATE, DELETE - old_values JSON, - new_values JSON, - user_id BIGINT, - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP -); - --- Create indexes for audit logs -CREATE INDEX idx_audit_logs_table_record ON audit_logs(table_name, record_id); -CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at); - --- Show table structure -DESCRIBE users; -DESCRIBE courses; -DESCRIBE branches; -DESCRIBE user_roles; -DESCRIBE user_role_mapping; -DESCRIBE audit_logs; - --- Show sample data -SELECT 'Users Table:' as table_name; -SELECT * FROM users LIMIT 5; - -SELECT 'Courses Table:' as table_name; -SELECT * FROM courses; - -SELECT 'Branches Table:' as table_name; -SELECT * FROM branches; - -SELECT 'User Roles Table:' as table_name; -SELECT * FROM user_roles; \ No newline at end of file diff --git a/backend/mvnw b/backend/mvnw old mode 100755 new mode 100644 diff --git a/backend/pom.xml b/backend/pom.xml index 9743e2f..48e3faa 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -1,64 +1,65 @@ - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.3.5 - - - com.student.registration - student-registration-backend - 0.0.1-SNAPSHOT - student-registration-backend - Demo project for Spring Boot - - - - - - - - - - - - - - - 17 - - - - org.springframework.boot - spring-boot-starter-web - + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 - - org.springframework.boot - spring-boot-starter-test - test - - - org.projectlombok - lombok - 1.18.30 - provided - - - org.springframework.boot - spring-boot-starter-actuator - - + + org.springframework.boot + spring-boot-starter-parent + 3.3.5 + + - - - - org.springframework.boot - spring-boot-maven-plugin - - - + com.student.registration + student-registration-backend + 0.0.1-SNAPSHOT + student-registration-backend + Demo project for Spring Boot + + + 17 + + + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.mariadb.jdbc + mariadb-java-client + runtime + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.projectlombok + lombok + 1.18.30 + provided + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + diff --git a/backend/simple_schema.sql b/backend/simple_schema.sql deleted file mode 100644 index 0e4a593..0000000 --- a/backend/simple_schema.sql +++ /dev/null @@ -1,40 +0,0 @@ --- Simple Database Schema for Student Registration System --- This is a minimal version with just the essential tables - --- Create database (uncomment and modify as needed) --- CREATE DATABASE student_db; --- USE student_db; - --- Drop existing tables if they exist -DROP TABLE IF EXISTS users; - --- Create the main users table -CREATE TABLE users ( - id BIGINT AUTO_INCREMENT PRIMARY KEY, - name VARCHAR(255) NOT NULL, - email VARCHAR(255) NOT NULL UNIQUE, - course VARCHAR(255), - student_class VARCHAR(100), - percentage DECIMAL(5,2), - branch VARCHAR(255), - mobile_number VARCHAR(20), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP -); - --- Create basic indexes -CREATE INDEX idx_users_email ON users(email); - --- Insert sample data -INSERT INTO users (name, email, course, student_class, percentage, branch, mobile_number) VALUES -('John Doe', 'john.doe@example.com', 'Computer Science', 'Final Year', 85.50, 'Computer Engineering', '+1234567890'), -('Jane Smith', 'jane.smith@example.com', 'Electrical Engineering', 'Third Year', 78.25, 'Electrical Engineering', '+1234567891'), -('Mike Johnson', 'mike.johnson@example.com', 'Mechanical Engineering', 'Second Year', 82.75, 'Mechanical Engineering', '+1234567892'), -('Sarah Wilson', 'sarah.wilson@example.com', 'Computer Science', 'First Year', 90.00, 'Computer Engineering', '+1234567893'), -('David Brown', 'david.brown@example.com', 'Civil Engineering', 'Final Year', 76.80, 'Civil Engineering', '+1234567894'); - --- Show table structure -DESCRIBE users; - --- Show sample data -SELECT * FROM users; \ No newline at end of file diff --git a/backend/src/main/java/com/student/registration/student_registration_backend/config/WebConfig.java b/backend/src/main/java/com/student/registration/student_registration_backend/config/WebConfig.java index 8d2258c..12dbbde 100644 --- a/backend/src/main/java/com/student/registration/student_registration_backend/config/WebConfig.java +++ b/backend/src/main/java/com/student/registration/student_registration_backend/config/WebConfig.java @@ -1,18 +1,18 @@ -package com.student.registration.student_registration_backend.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class WebConfig implements WebMvcConfigurer { - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/api/**") - .allowedOriginPatterns("*") - .allowedMethods("GET", "POST", "PUT", "DELETE") - .allowedHeaders("*") - .allowCredentials(true); - } -} +package com.student.registration.student_registration_backend.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/**") + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE") + .allowedHeaders("*") + .allowCredentials(true); + } +} diff --git a/backend/src/main/java/com/student/registration/student_registration_backend/controller/HealthController.java b/backend/src/main/java/com/student/registration/student_registration_backend/controller/HealthController.java deleted file mode 100644 index 05cf008..0000000 --- a/backend/src/main/java/com/student/registration/student_registration_backend/controller/HealthController.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.student.registration.student_registration_backend.controller; - -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -public class HealthController { - - @GetMapping("/health") - public ResponseEntity health() { - return ResponseEntity.ok("OK"); - } - - @GetMapping("/") - public ResponseEntity root() { - return ResponseEntity.ok("EasyCRUD Backend is running!"); - } -} \ No newline at end of file diff --git a/backend/src/main/java/com/student/registration/student_registration_backend/controller/UserController.java b/backend/src/main/java/com/student/registration/student_registration_backend/controller/UserController.java index 3e33569..fad9bc7 100644 --- a/backend/src/main/java/com/student/registration/student_registration_backend/controller/UserController.java +++ b/backend/src/main/java/com/student/registration/student_registration_backend/controller/UserController.java @@ -1,43 +1,38 @@ package com.student.registration.student_registration_backend.controller; import com.student.registration.student_registration_backend.model.User; +import com.student.registration.student_registration_backend.repository.UserRepository; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.concurrent.atomic.AtomicLong; @RestController @RequestMapping("/api") public class UserController { - private final List users = new ArrayList<>(); - private final AtomicLong idCounter = new AtomicLong(1); + @Autowired + private UserRepository userRepository; @PostMapping("/register") public User registerUser(@RequestBody User user) { - user.setId(idCounter.getAndIncrement()); - users.add(user); - return user; + return userRepository.save(user); } @GetMapping("/users") public List getAllUsers() { - return new ArrayList<>(users); + return userRepository.findAll(); } // Delete user by ID @DeleteMapping("/users/{id}") public ResponseEntity deleteUser(@PathVariable Long id) { - Optional userToDelete = users.stream() - .filter(user -> user.getId().equals(id)) - .findFirst(); - - if (userToDelete.isPresent()) { - users.remove(userToDelete.get()); + Optional user = userRepository.findById(id); + if (user.isPresent()) { + userRepository.deleteById(id); return ResponseEntity.ok("User deleted successfully"); } else { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found"); diff --git a/backend/src/main/java/com/student/registration/student_registration_backend/model/User.java b/backend/src/main/java/com/student/registration/student_registration_backend/model/User.java index dd8f854..0decdeb 100644 --- a/backend/src/main/java/com/student/registration/student_registration_backend/model/User.java +++ b/backend/src/main/java/com/student/registration/student_registration_backend/model/User.java @@ -1,10 +1,17 @@ package com.student.registration.student_registration_backend.model; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; import lombok.Data; +@Entity @Data // This annotation from Lombok automatically generates getters, setters, and other methods. public class User { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; diff --git a/backend/src/main/java/com/student/registration/student_registration_backend/repository/UserRepository.java b/backend/src/main/java/com/student/registration/student_registration_backend/repository/UserRepository.java new file mode 100644 index 0000000..a4acccc --- /dev/null +++ b/backend/src/main/java/com/student/registration/student_registration_backend/repository/UserRepository.java @@ -0,0 +1,9 @@ +package com.student.registration.student_registration_backend.repository; + +import com.student.registration.student_registration_backend.model.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends JpaRepository { +} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 4c00e40..9cce4ea 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1 +1,8 @@ -server.port=8080 +server.port=8080 + +spring.datasource.url=jdbc:mariadb://database-1.cyz6e8o02c2r.us-east-1.rds.amazonaws.com:3306/student_db?sslMode=trust +spring.datasource.username=admin +spring.datasource.password=redhat123 + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..70f6b2d --- /dev/null +++ b/compose.yml @@ -0,0 +1,20 @@ +version: "3.8" +services: + backend: + build: + context: ./backend + dockerfile: dockerfile + ports: + - 8080:8080 + # depends_on: + # -db + + frontend: + build: + context: ./frontend + dockerfile: dockerfile + ports: + - 80:80 + depends_on: + - backend + diff --git a/frontend/.env b/frontend/.env index 19cdadd..5d6b475 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,3 +1 @@ -VITE_API_URL=http://$BACKEND:8080/api -VITE_API_BASE_URL=http://$BACKEND:8080 -VITE_APP_TITLE=EasyCRUD Student Registration +VITE_API_URL=http://3.237.105.61:8080/api diff --git a/frontend/.gitignore b/frontend/.gitignore index 1421f2a..4108b33 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,148 +1,24 @@ -# Dependencies -node_modules/ +# Logs +logs +*.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* -# Build outputs -dist/ -build/ -*.tgz -*.tar.gz - -# Environment variables -.env.local -.env.development.local -.env.test.local -.env.production.local - -# IDE and Editor files -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# OS generated files -.DS_Store -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db - -# Logs -logs -*.log - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - -# Coverage directory used by tools like istanbul -coverage/ -*.lcov - -# nyc test coverage -.nyc_output - -# Dependency directories -jspm_packages/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional stylelint cache -.stylelintcache - -# Microbundle cache -.rpt2_cache/ -.rts2_cache_cjs/ -.rts2_cache_es/ -.rts2_cache_umd/ - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# parcel-bundler cache (https://parceljs.org/) -.cache -.parcel-cache - -# Next.js build output -.next - -# Nuxt.js build / generate output -.nuxt - -# Gatsby files -.cache/ -public - -# Storybook build outputs -.out -.storybook-out - -# Temporary folders -tmp/ -temp/ +node_modules +dist +dist-ssr +*.local # Editor directories and files .vscode/* !.vscode/extensions.json .idea +.DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? - -# Local Netlify folder -.netlify - -# Vite -.vite - -# TypeScript -*.tsbuildinfo - -# Testing -/coverage -/.nyc_output - -# Production -/build - -# Misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -# Debug -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -# Local env files -.env*.local - -# Vercel -.vercel - -# Turbo -.turbo diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 51e950b..9294a0e 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,77 +1,9 @@ -# Multi-stage build for React application -FROM node:18-alpine AS build - -# Set working directory -WORKDIR /app - -# Copy package files -COPY package*.json ./ - -# Install dependencies (including dev dependencies for build) -RUN npm ci - -# Copy source code -COPY . . - -# Build arguments for backend IP detection -ARG BACKEND_HOST -ARG BACKEND_PORT=8080 - -# Install curl for IP detection -RUN apk add --no-cache curl - -# Detect backend IP and update .env file -RUN if [ -n "$BACKEND_HOST" ]; then \ - # If BACKEND_HOST is provided, use it directly - BACKEND_IP="$BACKEND_HOST"; \ - else \ - # Try to detect the backend IP automatically - BACKEND_IP=$(curl -s ifconfig.me 2>/dev/null || echo "localhost"); \ - fi && \ - echo "Detected backend IP: $BACKEND_IP" && \ - if [ -f .env ]; then \ - sed -i "s/\$BACKEND/$BACKEND_IP/g" .env; \ - echo "Updated .env file with backend IP: $BACKEND_IP"; \ - cat .env; \ - else \ - echo "VITE_API_URL=http://$BACKEND_IP:$BACKEND_PORT/api" > .env; \ - echo "VITE_API_BASE_URL=http://$BACKEND_IP:$BACKEND_PORT" >> .env; \ - echo "VITE_APP_TITLE=EasyCRUD Student Registration" >> .env; \ - echo "Created .env file with backend IP: $BACKEND_IP"; \ - fi - -# Build the application -RUN npm run build - -# Production stage with nginx -FROM nginx:alpine - -# Install curl for health checks -RUN apk add --no-cache curl - -# Copy built application from build stage -COPY --from=build /app/dist /usr/share/nginx/html - -# Copy custom nginx configuration -COPY nginx-simple.conf /etc/nginx/conf.d/default.conf - -# Use existing nginx user (already exists in nginx:alpine) - -# Change ownership of nginx directories -RUN chown -R nginx:nginx /var/cache/nginx && \ - chown -R nginx:nginx /var/log/nginx && \ - chown -R nginx:nginx /etc/nginx/conf.d && \ - chown -R nginx:nginx /usr/share/nginx/html - -# Run as root for proper nginx startup -USER root - -# Expose port -EXPOSE 3000 - -# Health check -HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ - CMD curl -f http://localhost:3000 || exit 1 - -# Start nginx -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file +FROM node:24-alpine +COPY . /opt/ +WORKDIR /opt +RUN npm install && npm run build +RUN apk update && apk add apache2 +RUN rm -rf /var/www/localhost/htdocs/* +RUN cp -rf dist/* /var/www/localhost/htdocs +EXPOSE 80 +ENTRYPOINT ["httpd","-D","FOREGROUND"] diff --git a/frontend/README.md b/frontend/README.md index 1d133af..697698d 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -1,358 +1,56 @@ -# React Project Setup and Deployment Guide for Windows - -This guide provides step-by-step instructions for setting up a React project. - -## 1. Setting Up the React Project - -### Install Node.js and npm - -```shell -apt update && apt install nodejs npm -y -``` - -### Verify Installation - - -```shell -node -v -npm -v -``` - - -## 2. Install Dependencies - -To install the necessary dependencies for your project, run the following command: - -```shell -npm install -``` - -## 3. Build the React Application for Production - -Update backend URL in .env file - -```bash -vim .env -``` -```shell -VITE_API_URL=http://$BACKEND:8080/api -VITE_API_BASE_URL=http://$BACKEND:8080 -VITE_APP_TITLE=EasyCRUD Student Registration -``` - -To build the React application for production, run: - -```shell -npm run build -``` - -This will create a dist/ directory in your project containing optimized, production-ready files. - -## 4. Deploy production-ready files on s3 or apache2 server - -```shell -apt install apache2 -y -systemctl start apache2 -cp -rf dist/* /var/www/html/ -``` - -You can access the application on http://localhost:80 - - ---- - -# CLOUDBLITZ Student Management Frontend - -A modern, scalable React application built with industry best practices for managing student registrations and data. - -## ๐Ÿš€ Features - -- **Modern React Architecture**: Built with React 18, React Router v6, and modern hooks -- **State Management**: React Query for server state, Context API for global state -- **Type Safety**: Full TypeScript support (can be easily migrated) -- **Responsive Design**: Mobile-first approach with modern CSS -- **Error Handling**: Comprehensive error boundaries and user feedback -- **Performance**: Optimized with React Query caching and lazy loading -- **Accessibility**: WCAG compliant with proper focus management -- **Developer Experience**: Hot reloading, ESLint, and development tools - -## ๐Ÿ“ Project Structure - -``` -src/ -โ”œโ”€โ”€ components/ # Reusable UI components -โ”‚ โ”œโ”€โ”€ common/ # Shared components (ErrorBoundary, etc.) -โ”‚ โ””โ”€โ”€ layout/ # Layout components (Header, Navigation, etc.) -โ”œโ”€โ”€ context/ # React Context providers -โ”œโ”€โ”€ hooks/ # Custom React hooks -โ”œโ”€โ”€ pages/ # Page components -โ”œโ”€โ”€ api/ # API service functions -โ”œโ”€โ”€ assets/ # Static assets (images, icons) -โ”œโ”€โ”€ styles/ # Global styles and utilities -โ”œโ”€โ”€ utils/ # Utility functions -โ”œโ”€โ”€ App.jsx # Main application component -โ”œโ”€โ”€ main.jsx # Application entry point -โ””โ”€โ”€ index.css # Global styles -``` - -## ๐Ÿ› ๏ธ Technology Stack - -- **React 18** - UI library -- **React Router v6** - Client-side routing -- **React Query (TanStack Query)** - Server state management -- **React Icons** - Icon library -- **React Hot Toast** - Toast notifications -- **Axios** - HTTP client -- **Vite** - Build tool and dev server -- **ESLint** - Code linting - -## ๐Ÿš€ Getting Started - -### Prerequisites - -- Node.js 18+ -- npm or yarn - -### Installation - -1. Install dependencies: -```bash -npm install -``` - -2. Start development server: -```bash -npm run dev -``` - -3. Build for production: -```bash -npm run build -``` - -## ๐Ÿ“‹ Available Scripts - -- `npm run dev` - Start development server -- `npm run build` - Build for production -- `npm run preview` - Preview production build -- `npm run lint` - Run ESLint - -## ๐Ÿ—๏ธ Architecture Overview - -### Component Structure - -The application follows a hierarchical component structure: - -1. **App Component** - Main application wrapper with providers -2. **Layout Component** - Consistent layout with navigation -3. **Page Components** - Individual page views -4. **Common Components** - Reusable UI elements - -### State Management - -- **React Query**: Handles server state (API calls, caching, synchronization) -- **Context API**: Manages global application state (theme, user, notifications) -- **Local State**: Component-specific state using useState/useReducer - -### Routing - -Uses React Router v6 with: -- Nested routes -- Route protection (can be implemented) -- 404 handling -- Programmatic navigation - -## ๐ŸŽจ Styling Approach - -### CSS Architecture - -- **Global Styles**: Reset, base styles, and utility classes -- **Component Styles**: Scoped CSS files for each component -- **Responsive Design**: Mobile-first with breakpoint utilities -- **Design System**: Consistent colors, spacing, and typography - -### Design Tokens - -```css -/* Colors */ ---primary: #ff8000; ---primary-dark: #e67300; ---text-primary: #1f2937; ---text-secondary: #6b7280; - -/* Spacing */ ---spacing-xs: 0.25rem; ---spacing-sm: 0.5rem; ---spacing-md: 1rem; ---spacing-lg: 1.5rem; ---spacing-xl: 2rem; -``` - -## ๐Ÿ”ง Development Guidelines - -### Code Style - -- Use functional components with hooks -- Prefer composition over inheritance -- Follow React best practices -- Use meaningful component and variable names -- Add proper TypeScript types (when migrated) - -### Component Guidelines - -```jsx -// Good component structure -const ComponentName = ({ prop1, prop2 }) => { - // 1. Hooks - const [state, setState] = useState(); - - // 2. Event handlers - const handleClick = () => {}; - - // 3. Effects - useEffect(() => {}, []); - - // 4. Render - return
Content
; -}; -``` - -### File Naming - -- Components: PascalCase (e.g., `UserProfile.jsx`) -- Pages: PascalCase (e.g., `Dashboard.jsx`) -- Utilities: camelCase (e.g., `formatDate.js`) -- Styles: kebab-case (e.g., `user-profile.css`) - -## ๐Ÿ“ฑ Responsive Design - -The application is built with a mobile-first approach: - -- **Mobile**: < 768px -- **Tablet**: 768px - 1024px -- **Desktop**: > 1024px - -### Breakpoints - -```css -@media (max-width: 768px) { /* Mobile styles */ } -@media (max-width: 1024px) { /* Tablet styles */ } -@media (min-width: 1025px) { /* Desktop styles */ } -``` - -## ๐Ÿ”’ Security Considerations - -- Input validation on both client and server -- XSS prevention with proper escaping -- CSRF protection (implemented on backend) -- Secure HTTP headers -- Content Security Policy (CSP) - -## ๐Ÿงช Testing Strategy - -### Testing Levels - -1. **Unit Tests**: Individual components and functions -2. **Integration Tests**: Component interactions -3. **E2E Tests**: User workflows (can be implemented with Cypress) - -### Testing Tools - -- React Testing Library -- Jest -- MSW (Mock Service Worker) for API mocking - -## ๐Ÿ“Š Performance Optimization - -### React Optimizations - -- React.memo for expensive components -- useMemo for expensive calculations -- useCallback for stable function references -- Lazy loading for routes - -### Bundle Optimization - -- Code splitting with React.lazy -- Tree shaking -- Dynamic imports -- Asset optimization - -## ๐ŸŒ Browser Support - -- Chrome 90+ -- Firefox 88+ -- Safari 14+ -- Edge 90+ - -## ๐Ÿ”„ API Integration - -### API Service Structure - -```javascript -// api/userService.js -export const fetchUsers = async () => { - const response = await axios.get('/api/users'); - return response.data; -}; -``` - -### Error Handling - -- Global error boundary -- API error handling with React Query -- User-friendly error messages -- Retry mechanisms - -## ๐Ÿ“ˆ Monitoring and Analytics - -- Error tracking (can be integrated with Sentry) -- Performance monitoring -- User analytics (can be integrated with Google Analytics) -- Logging and debugging tools - -## ๐Ÿš€ Deployment - -### Build Process - -1. Run `npm run build` -2. Optimized files generated in `dist/` -3. Deploy to hosting service - -### Environment Variables - -```bash -VITE_API_BASE_URL=http://localhost:8080 -VITE_APP_NAME=CLOUDBLITZ Student Management -``` - -## ๐Ÿค Contributing - -1. Fork the repository -2. Create a feature branch -3. Make your changes -4. Add tests if applicable -5. Submit a pull request - -## ๐Ÿ“„ License - -This project is licensed under the MIT License. - -## ๐Ÿ†˜ Support - -For support and questions: -- Create an issue in the repository -- Contact the development team -- Check the documentation - -## ๐Ÿ”ฎ Future Enhancements - -- [ ] TypeScript migration -- [ ] Advanced filtering and search -- [ ] Data export functionality -- [ ] Real-time updates with WebSocket -- [ ] Offline support with Service Workers -- [ ] Advanced analytics dashboard -- [ ] Multi-language support -- [ ] Dark mode theme -- [ ] Advanced user roles and permissions +# React Project Setup and Deployment Guide for Windows + +This guide provides step-by-step instructions for setting up a React project. + +## 1. Setting Up the React Project + +### Install Node.js and npm + +```shell +apt update && apt install nodejs npm -y +``` + +### Verify Installation + + +```shell +node -v +npm -v +``` + + +## 2. Install Dependencies + +To install the necessary dependencies for your project, run the following command: + +```shell +npm install +``` + +## 3. Build the React Application for Production + +Update backend URL in .env file + +```shell +vim .env + + VITE_API_URL = "http://:8080/api" +``` + +To build the React application for production, run: + +```shell +npm run build +``` + +This will create a dist/ directory in your project containing optimized, production-ready files. + +## 4. Deploy production-ready files on s3 or apache2 server + +```shell +apt install apache2 -y +systemctl start apache2 +cp -rf dist/* /var/www/html/ +``` + +You can access the application on http://localhost:80 \ No newline at end of file diff --git a/frontend/frontend-deploy.yml b/frontend/frontend-deploy.yml new file mode 100644 index 0000000..5dc1d23 --- /dev/null +++ b/frontend/frontend-deploy.yml @@ -0,0 +1,22 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend + labels: + app: frontend +spec: + replicas: 3 + selector: + matchLabels: + app: frontend + template: + metadata: + name: frontend + labels: + app: frontend + spec: + containers: + - name: frontend + image: r123mahajan/frontend:latest + ports: + - containerPort: 80 diff --git a/frontend/frontend-svc.yml b/frontend/frontend-svc.yml new file mode 100644 index 0000000..2a5419e --- /dev/null +++ b/frontend/frontend-svc.yml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Service +metadata: + name: frontend-svc +spec: + type: LoadBalancer + selector: + app: frontend + ports: + - port: 80 + targetPort: 80 diff --git a/frontend/index.html b/frontend/index.html index 90d86bf..77a4b65 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,13 +1,13 @@ - - - - - - - CLOUDBLITZ | Student Registration - - -
- - - + + + + + + + CLOUDBLITZ | Student Registration + + +
+ + + diff --git a/frontend/nginx-simple.conf b/frontend/nginx-simple.conf deleted file mode 100644 index 2d2caa7..0000000 --- a/frontend/nginx-simple.conf +++ /dev/null @@ -1,55 +0,0 @@ -server { - listen 3000; - server_name localhost; - root /usr/share/nginx/html; - index index.html; - - # Enable gzip compression - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_types text/plain text/css text/xml text/javascript application/javascript; - - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-XSS-Protection "1; mode=block" always; - add_header X-Content-Type-Options "nosniff" always; - - # Handle React Router (SPA) - location / { - try_files $uri $uri/ /index.html; - } - - # Cache static assets - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - try_files $uri =404; - } - - # API proxy (if needed) - location /api/ { - proxy_pass http://backend:8080/api/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 30s; - } - - # Health check endpoint - location /health { - access_log off; - return 200 "healthy\n"; - add_header Content-Type text/plain; - } - - # Error pages - error_page 404 /index.html; - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } -} \ No newline at end of file diff --git a/frontend/nginx.conf b/frontend/nginx.conf deleted file mode 100644 index ce95886..0000000 --- a/frontend/nginx.conf +++ /dev/null @@ -1,58 +0,0 @@ -server { - listen 3000; - server_name localhost; - root /usr/share/nginx/html; - index index.html; - - # Enable gzip compression - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_proxied expired no-cache no-store private auth; - gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript; - - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-XSS-Protection "1; mode=block" always; - add_header X-Content-Type-Options "nosniff" always; - add_header Referrer-Policy "no-referrer-when-downgrade" always; - add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; - - # Handle React Router (SPA) - location / { - try_files $uri $uri/ /index.html; - } - - # Cache static assets - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - try_files $uri =404; - } - - # API proxy (if needed) - location /api/ { - proxy_pass http://backend:8080/api/; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 30s; - } - - # Health check endpoint - location /health { - access_log off; - return 200 "healthy\n"; - add_header Content-Type text/plain; - } - - # Error pages - error_page 404 /index.html; - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } -} \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index de4746e..29aa827 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,14 +8,10 @@ "name": "student-registration-frontend", "version": "0.0.0", "dependencies": { - "@tanstack/react-query": "^5.59.16", - "@tanstack/react-query-devtools": "^5.59.16", "@testing-library/react": "^16.1.0", "axios": "^1.7.7", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-hot-toast": "^2.4.1", - "react-icons": "^5.0.1", "react-router-dom": "^6.28.0", "web-vitals": "^4.2.4" }, @@ -1246,59 +1242,6 @@ "win32" ] }, - "node_modules/@tanstack/query-core": { - "version": "5.83.0", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.83.0.tgz", - "integrity": "sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/query-devtools": { - "version": "5.81.2", - "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.81.2.tgz", - "integrity": "sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "5.83.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.83.0.tgz", - "integrity": "sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ==", - "license": "MIT", - "dependencies": { - "@tanstack/query-core": "5.83.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^18 || ^19" - } - }, - "node_modules/@tanstack/react-query-devtools": { - "version": "5.83.0", - "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.83.0.tgz", - "integrity": "sha512-yfp8Uqd3I1jgx8gl0lxbSSESu5y4MO2ThOPBnGNTYs0P+ZFu+E9g5IdOngyUGuo6Uz6Qa7p9TLdZEX3ntik2fQ==", - "license": "MIT", - "dependencies": { - "@tanstack/query-devtools": "5.81.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "@tanstack/react-query": "^5.83.0", - "react": "^18 || ^19" - } - }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -1893,6 +1836,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", + "devOptional": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -2756,15 +2700,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/goober": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", - "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", - "license": "MIT", - "peerDependencies": { - "csstype": "^3.0.10" - } - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -3875,32 +3810,6 @@ "react": "^18.3.1" } }, - "node_modules/react-hot-toast": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", - "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==", - "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", - "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", - "license": "MIT", - "peerDependencies": { - "react": "*" - } - }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index dde43e8..7d7b962 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,36 +1,32 @@ -{ - "name": "student-registration-frontend", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "@tanstack/react-query": "^5.59.16", - "@tanstack/react-query-devtools": "^5.59.16", - "@testing-library/react": "^16.1.0", - "axios": "^1.7.7", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-hot-toast": "^2.4.1", - "react-icons": "^5.0.1", - "react-router-dom": "^6.28.0", - "web-vitals": "^4.2.4" - }, - "devDependencies": { - "@eslint/js": "^9.13.0", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.3.3", - "eslint": "^9.13.0", - "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-react-refresh": "^0.4.14", - "globals": "^15.11.0", - "vite": "^5.4.10" - } -} +{ + "name": "student-registration-frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@testing-library/react": "^16.1.0", + "axios": "^1.7.7", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.28.0", + "web-vitals": "^4.2.4" + }, + "devDependencies": { + "@eslint/js": "^9.13.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "eslint": "^9.13.0", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.11.0", + "vite": "^5.4.10" + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 4d02693..a86b4a2 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,78 +1,15 @@ -import React from 'react'; -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; -import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; -import { Toaster } from 'react-hot-toast'; - -// Layout Components -import Layout from './components/layout/Layout'; -import ErrorBoundary from './components/common/ErrorBoundary'; - -// Pages -import Dashboard from './pages/Dashboard'; -import StudentRegistration from './pages/StudentRegistration'; -import StudentList from './pages/StudentList'; -import NotFound from './pages/NotFound'; - -// Context Providers -import { AppProvider } from './context/AppContext'; - -// Create a client for React Query -const queryClient = new QueryClient({ - defaultOptions: { - queries: { - staleTime: 5 * 60 * 1000, // 5 minutes - cacheTime: 10 * 60 * 1000, // 10 minutes - retry: 1, - refetchOnWindowFocus: false, - }, - }, -}); - -const App = () => { - return ( - - - - - - - } /> - } /> - } /> - } /> - - - - - - - - - ); -}; - -export default App; +import React from "react"; +import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; +import RegistrationForm from "./components/RegistrationForm"; + +const App = () => { + return ( + + + } /> + + + ); +}; + +export default App; diff --git a/frontend/src/api/userService.js b/frontend/src/api/userService.js index 339a962..ac4c93c 100644 --- a/frontend/src/api/userService.js +++ b/frontend/src/api/userService.js @@ -1,33 +1,31 @@ -import axios from "axios"; -import { getApiUrl } from "../utils/config.js"; - -const BASE_URL = getApiUrl(); -console.log('userService BASE_URL:', BASE_URL); // Debug log - -export const fetchUsers = async () => { - try { - const response = await axios.get(`${BASE_URL}/users`); - return response.data; - } catch (error) { - console.error("Error fetching users:", error); - throw error; - } -}; - -export const registerUser = async (userData) => { - try { - await axios.post(`${BASE_URL}/register`, userData); - } catch (error) { - console.error("Error registering user:", error); - throw error; - } -}; - -export const deleteUser = async (id) => { - try { - await axios.delete(`${BASE_URL}/users/${id}`); - } catch (error) { - console.error("Error deleting user:", error); - throw error; - } -}; +import axios from "axios"; + +const BASE_URL = import.meta.env.VITE_API_URL; + +export const fetchUsers = async () => { + try { + const response = await axios.get(`${BASE_URL}/users`); + return response.data; + } catch (error) { + console.error("Error fetching users:", error); + throw error; + } +}; + +export const registerUser = async (userData) => { + try { + await axios.post(`${BASE_URL}/register`, userData); + } catch (error) { + console.error("Error registering user:", error); + throw error; + } +}; + +export const deleteUser = async (id) => { + try { + await axios.delete(`${BASE_URL}/users/${id}`); + } catch (error) { + console.error("Error deleting user:", error); + throw error; + } +}; diff --git a/frontend/src/components/common/ErrorBoundary.css b/frontend/src/components/common/ErrorBoundary.css deleted file mode 100644 index bc8230a..0000000 --- a/frontend/src/components/common/ErrorBoundary.css +++ /dev/null @@ -1,191 +0,0 @@ -.error-boundary { - min-height: 100vh; - display: flex; - align-items: center; - justify-content: center; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - padding: 2rem; -} - -.error-container { - background: white; - border-radius: 1rem; - padding: 3rem; - text-align: center; - max-width: 600px; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); - animation: slideIn 0.5s ease-out; -} - -.error-icon { - font-size: 4rem; - color: #ef4444; - margin-bottom: 1.5rem; - animation: pulse 2s infinite; -} - -.error-title { - font-size: 2rem; - font-weight: 700; - color: #1f2937; - margin-bottom: 1rem; -} - -.error-message { - font-size: 1.125rem; - color: #6b7280; - line-height: 1.6; - margin-bottom: 2rem; -} - -.error-actions { - display: flex; - gap: 1rem; - justify-content: center; - flex-wrap: wrap; - margin-bottom: 2rem; -} - -.error-button { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1.5rem; - border: none; - border-radius: 0.5rem; - font-size: 1rem; - font-weight: 500; - cursor: pointer; - transition: all 0.3s ease; - text-decoration: none; -} - -.error-button.primary { - background: #ff8000; - color: white; -} - -.error-button.primary:hover { - background: #e67300; - transform: translateY(-2px); - box-shadow: 0 4px 12px rgba(255, 128, 0, 0.3); -} - -.error-button.secondary { - background: #f3f4f6; - color: #374151; - border: 1px solid #d1d5db; -} - -.error-button.secondary:hover { - background: #e5e7eb; - transform: translateY(-2px); -} - -.button-icon { - font-size: 1rem; -} - -.error-details { - margin-top: 2rem; - text-align: left; - border-top: 1px solid #e5e7eb; - padding-top: 1rem; -} - -.error-details summary { - cursor: pointer; - font-weight: 600; - color: #374151; - margin-bottom: 1rem; -} - -.error-details summary:hover { - color: #ff8000; -} - -.error-stack { - background: #f9fafb; - border: 1px solid #e5e7eb; - border-radius: 0.5rem; - padding: 1rem; - font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; - font-size: 0.875rem; - line-height: 1.5; - overflow-x: auto; -} - -.error-stack h4 { - margin: 0 0 0.5rem 0; - color: #374151; - font-size: 0.875rem; -} - -.error-stack pre { - margin: 0 0 1rem 0; - color: #ef4444; - white-space: pre-wrap; - word-break: break-word; -} - -/* Animations */ -@keyframes slideIn { - from { - opacity: 0; - transform: translateY(30px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes pulse { - 0%, 100% { - transform: scale(1); - } - 50% { - transform: scale(1.05); - } -} - -/* Responsive Design */ -@media (max-width: 768px) { - .error-container { - padding: 2rem; - margin: 1rem; - } - - .error-title { - font-size: 1.5rem; - } - - .error-message { - font-size: 1rem; - } - - .error-actions { - flex-direction: column; - align-items: center; - } - - .error-button { - width: 100%; - max-width: 200px; - justify-content: center; - } -} - -@media (max-width: 480px) { - .error-boundary { - padding: 1rem; - } - - .error-container { - padding: 1.5rem; - } - - .error-icon { - font-size: 3rem; - } -} \ No newline at end of file diff --git a/frontend/src/components/common/ErrorBoundary.jsx b/frontend/src/components/common/ErrorBoundary.jsx deleted file mode 100644 index ec3b1cf..0000000 --- a/frontend/src/components/common/ErrorBoundary.jsx +++ /dev/null @@ -1,82 +0,0 @@ -import React from 'react'; -import { FaExclamationTriangle, FaHome, FaRedo } from 'react-icons/fa'; -import './ErrorBoundary.css'; - -class ErrorBoundary extends React.Component { - constructor(props) { - super(props); - this.state = { hasError: false, error: null, errorInfo: null }; - } - - static getDerivedStateFromError(error) { - return { hasError: true }; - } - - componentDidCatch(error, errorInfo) { - this.setState({ - error: error, - errorInfo: errorInfo, - }); - - // Log error to console in development - if (process.env.NODE_ENV === 'development') { - console.error('Error caught by boundary:', error, errorInfo); - } - - // In production, you would send this to an error reporting service - // Example: Sentry.captureException(error, { extra: errorInfo }); - } - - handleReload = () => { - window.location.reload(); - }; - - handleGoHome = () => { - window.location.href = '/'; - }; - - render() { - if (this.state.hasError) { - return ( -
-
-
- -
-

Oops! Something went wrong

-

- We're sorry, but something unexpected happened. Our team has been notified and is working to fix the issue. -

- -
- - -
- - {process.env.NODE_ENV === 'development' && this.state.error && ( -
- Error Details (Development Only) -
-

Error:

-
{this.state.error.toString()}
-

Component Stack:

-
{this.state.errorInfo.componentStack}
-
-
- )} -
-
- ); - } - - return this.props.children; - } -} - -export default ErrorBoundary; \ No newline at end of file diff --git a/frontend/src/components/layout/Layout.css b/frontend/src/components/layout/Layout.css deleted file mode 100644 index 96948ab..0000000 --- a/frontend/src/components/layout/Layout.css +++ /dev/null @@ -1,225 +0,0 @@ -/* Layout Styles */ -.layout { - min-height: 100vh; - display: flex; - flex-direction: column; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -} - -/* Header Styles */ -.header { - background: rgba(255, 255, 255, 0.95); - backdrop-filter: blur(10px); - border-bottom: 1px solid rgba(255, 255, 255, 0.2); - box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1); - position: sticky; - top: 0; - z-index: 1000; -} - -.header-content { - max-width: 1200px; - margin: 0 auto; - padding: 1rem 2rem; - display: flex; - justify-content: space-between; - align-items: center; -} - -.logo { - display: flex; - align-items: center; - gap: 0.75rem; -} - -.logo-icon { - font-size: 2rem; - color: #ff8000; -} - -.logo-text { - display: flex; - flex-direction: column; - font-size: 1.25rem; - font-weight: 700; - margin: 0; -} - -.logo-primary { - color: #ff8000; - font-size: 1.5rem; -} - -.logo-secondary { - color: #374151; - font-size: 0.875rem; - font-weight: 500; -} - -/* Navigation Styles */ -.navigation { - display: flex; - gap: 1rem; - align-items: center; -} - -.nav-link { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1rem; - text-decoration: none; - color: #374151; - font-weight: 500; - border-radius: 0.5rem; - transition: all 0.3s ease; - position: relative; -} - -.nav-link:hover { - background: rgba(255, 128, 0, 0.1); - color: #ff8000; - transform: translateY(-2px); -} - -.nav-link.active { - background: #ff8000; - color: white; - box-shadow: 0 4px 12px rgba(255, 128, 0, 0.3); -} - -.nav-icon { - font-size: 1.125rem; -} - -.nav-label { - font-size: 0.875rem; -} - -/* Main Content Styles */ -.main-content { - flex: 1; - padding: 2rem 0; -} - -.container { - max-width: 1200px; - margin: 0 auto; - padding: 0 2rem; -} - -/* Footer Styles */ -.footer { - background: rgba(0, 0, 0, 0.8); - color: white; - padding: 1.5rem 0; - margin-top: auto; -} - -.footer-content { - max-width: 1200px; - margin: 0 auto; - padding: 0 2rem; - display: flex; - justify-content: space-between; - align-items: center; -} - -.footer-links { - display: flex; - gap: 1.5rem; -} - -.footer-link { - color: #d1d5db; - text-decoration: none; - font-size: 0.875rem; - transition: color 0.3s ease; -} - -.footer-link:hover { - color: #ff8000; -} - -/* Responsive Design */ -@media (max-width: 768px) { - .header-content { - flex-direction: column; - gap: 1rem; - padding: 1rem; - } - - .navigation { - width: 100%; - justify-content: center; - flex-wrap: wrap; - } - - .nav-link { - padding: 0.5rem 0.75rem; - font-size: 0.875rem; - } - - .nav-label { - display: none; - } - - .container { - padding: 0 1rem; - } - - .footer-content { - flex-direction: column; - gap: 1rem; - text-align: center; - } - - .footer-links { - justify-content: center; - } -} - -@media (max-width: 480px) { - .logo-text { - font-size: 1rem; - } - - .logo-primary { - font-size: 1.25rem; - } - - .navigation { - gap: 0.5rem; - } - - .nav-link { - padding: 0.5rem; - } -} - -/* Animation Classes */ -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.layout { - animation: fadeIn 0.5s ease-out; -} - -/* Focus States for Accessibility */ -.nav-link:focus { - outline: 2px solid #ff8000; - outline-offset: 2px; -} - -.footer-link:focus { - outline: 2px solid #ff8000; - outline-offset: 2px; -} \ No newline at end of file diff --git a/frontend/src/components/layout/Layout.jsx b/frontend/src/components/layout/Layout.jsx deleted file mode 100644 index b8b000e..0000000 --- a/frontend/src/components/layout/Layout.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import React from 'react'; -import { Link, useLocation } from 'react-router-dom'; -import { FaGraduationCap, FaUserPlus, FaUsers, FaHome } from 'react-icons/fa'; -import './Layout.css'; - -const Layout = ({ children }) => { - const location = useLocation(); - - const navigationItems = [ - { path: '/', label: 'Dashboard', icon: FaHome }, - { path: '/register', label: 'Register Student', icon: FaUserPlus }, - { path: '/students', label: 'Student List', icon: FaUsers }, - ]; - - const isActiveRoute = (path) => location.pathname === path; - - return ( -
- {/* Header */} -
-
-
- -

- CLOUDBLITZ - Student Management -

-
- -
-
- - {/* Main Content */} -
-
- {children} -
-
- - {/* Footer */} - -
- ); -}; - -export default Layout; \ No newline at end of file diff --git a/frontend/src/context/AppContext.jsx b/frontend/src/context/AppContext.jsx deleted file mode 100644 index 4910da9..0000000 --- a/frontend/src/context/AppContext.jsx +++ /dev/null @@ -1,148 +0,0 @@ -import React, { createContext, useContext, useReducer, useEffect } from 'react'; -import { useQueryClient } from '@tanstack/react-query'; - -// Initial state -const initialState = { - theme: 'light', - user: null, - notifications: [], - isLoading: false, - error: null, -}; - -// Action types -const ACTIONS = { - SET_THEME: 'SET_THEME', - SET_USER: 'SET_USER', - SET_LOADING: 'SET_LOADING', - SET_ERROR: 'SET_ERROR', - ADD_NOTIFICATION: 'ADD_NOTIFICATION', - REMOVE_NOTIFICATION: 'REMOVE_NOTIFICATION', - CLEAR_NOTIFICATIONS: 'CLEAR_NOTIFICATIONS', -}; - -// Reducer function -const appReducer = (state, action) => { - switch (action.type) { - case ACTIONS.SET_THEME: - return { - ...state, - theme: action.payload, - }; - case ACTIONS.SET_USER: - return { - ...state, - user: action.payload, - }; - case ACTIONS.SET_LOADING: - return { - ...state, - isLoading: action.payload, - }; - case ACTIONS.SET_ERROR: - return { - ...state, - error: action.payload, - }; - case ACTIONS.ADD_NOTIFICATION: - return { - ...state, - notifications: [...state.notifications, action.payload], - }; - case ACTIONS.REMOVE_NOTIFICATION: - return { - ...state, - notifications: state.notifications.filter( - (notification) => notification.id !== action.payload - ), - }; - case ACTIONS.CLEAR_NOTIFICATIONS: - return { - ...state, - notifications: [], - }; - default: - return state; - } -}; - -// Create context -const AppContext = createContext(); - -// Custom hook to use the context -export const useApp = () => { - const context = useContext(AppContext); - if (!context) { - throw new Error('useApp must be used within an AppProvider'); - } - return context; -}; - -// Provider component -export const AppProvider = ({ children }) => { - const [state, dispatch] = useReducer(appReducer, initialState); - const queryClient = useQueryClient(); - - // Load theme from localStorage on mount - useEffect(() => { - const savedTheme = localStorage.getItem('theme'); - if (savedTheme) { - dispatch({ type: ACTIONS.SET_THEME, payload: savedTheme }); - } - }, []); - - // Save theme to localStorage when it changes - useEffect(() => { - localStorage.setItem('theme', state.theme); - document.documentElement.setAttribute('data-theme', state.theme); - }, [state.theme]); - - // Actions - const actions = { - setTheme: (theme) => { - dispatch({ type: ACTIONS.SET_THEME, payload: theme }); - }, - setUser: (user) => { - dispatch({ type: ACTIONS.SET_USER, payload: user }); - }, - setLoading: (isLoading) => { - dispatch({ type: ACTIONS.SET_LOADING, payload: isLoading }); - }, - setError: (error) => { - dispatch({ type: ACTIONS.SET_ERROR, payload: error }); - }, - addNotification: (notification) => { - const id = Date.now().toString(); - const newNotification = { - id, - timestamp: new Date(), - ...notification, - }; - dispatch({ type: ACTIONS.ADD_NOTIFICATION, payload: newNotification }); - - // Auto-remove notification after 5 seconds - setTimeout(() => { - actions.removeNotification(id); - }, 5000); - }, - removeNotification: (id) => { - dispatch({ type: ACTIONS.REMOVE_NOTIFICATION, payload: id }); - }, - clearNotifications: () => { - dispatch({ type: ACTIONS.CLEAR_NOTIFICATIONS }); - }, - clearCache: () => { - queryClient.clear(); - }, - invalidateQueries: (queryKey) => { - queryClient.invalidateQueries({ queryKey }); - }, - }; - - const value = { - ...state, - ...actions, - }; - - return {children}; -}; \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css deleted file mode 100644 index db61e00..0000000 --- a/frontend/src/index.css +++ /dev/null @@ -1,735 +0,0 @@ -/* Global CSS Reset and Base Styles */ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -html { - scroll-behavior: smooth; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - line-height: 1.6; - color: #1f2937; - background: #f9fafb; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} - -/* Remove default button styles */ -button { - font-family: inherit; - cursor: pointer; -} - -/* Remove default input styles */ -input, select, textarea { - font-family: inherit; -} - -/* Remove default link styles */ -a { - text-decoration: none; - color: inherit; -} - -/* Focus styles for accessibility */ -*:focus { - outline: 2px solid #ff8000; - outline-offset: 2px; -} - -/* Custom scrollbar */ -::-webkit-scrollbar { - width: 8px; -} - -::-webkit-scrollbar-track { - background: #f1f5f9; -} - -::-webkit-scrollbar-thumb { - background: #cbd5e1; - border-radius: 4px; -} - -::-webkit-scrollbar-thumb:hover { - background: #94a3b8; -} - -/* Utility classes */ -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} - -.text-center { - text-align: center; -} - -.text-left { - text-align: left; -} - -.text-right { - text-align: right; -} - -.font-bold { - font-weight: 700; -} - -.font-semibold { - font-weight: 600; -} - -.font-medium { - font-weight: 500; -} - -.text-sm { - font-size: 0.875rem; -} - -.text-base { - font-size: 1rem; -} - -.text-lg { - font-size: 1.125rem; -} - -.text-xl { - font-size: 1.25rem; -} - -.text-2xl { - font-size: 1.5rem; -} - -.text-3xl { - font-size: 1.875rem; -} - -.text-4xl { - font-size: 2.25rem; -} - -.text-gray-500 { - color: #6b7280; -} - -.text-gray-600 { - color: #4b5563; -} - -.text-gray-700 { - color: #374151; -} - -.text-gray-800 { - color: #1f2937; -} - -.text-gray-900 { - color: #111827; -} - -.text-orange-500 { - color: #ff8000; -} - -.text-orange-600 { - color: #e67300; -} - -.bg-white { - background-color: white; -} - -.bg-gray-50 { - background-color: #f9fafb; -} - -.bg-gray-100 { - background-color: #f3f4f6; -} - -.bg-orange-500 { - background-color: #ff8000; -} - -.bg-orange-600 { - background-color: #e67300; -} - -.rounded { - border-radius: 0.25rem; -} - -.rounded-md { - border-radius: 0.375rem; -} - -.rounded-lg { - border-radius: 0.5rem; -} - -.rounded-xl { - border-radius: 0.75rem; -} - -.rounded-2xl { - border-radius: 1rem; -} - -.shadow { - box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); -} - -.shadow-md { - box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); -} - -.shadow-lg { - box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); -} - -.shadow-xl { - box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); -} - -.p-1 { - padding: 0.25rem; -} - -.p-2 { - padding: 0.5rem; -} - -.p-3 { - padding: 0.75rem; -} - -.p-4 { - padding: 1rem; -} - -.p-6 { - padding: 1.5rem; -} - -.p-8 { - padding: 2rem; -} - -.px-2 { - padding-left: 0.5rem; - padding-right: 0.5rem; -} - -.px-4 { - padding-left: 1rem; - padding-right: 1rem; -} - -.px-6 { - padding-left: 1.5rem; - padding-right: 1.5rem; -} - -.py-2 { - padding-top: 0.5rem; - padding-bottom: 0.5rem; -} - -.py-4 { - padding-top: 1rem; - padding-bottom: 1rem; -} - -.py-6 { - padding-top: 1.5rem; - padding-bottom: 1.5rem; -} - -.m-1 { - margin: 0.25rem; -} - -.m-2 { - margin: 0.5rem; -} - -.m-4 { - margin: 1rem; -} - -.m-6 { - margin: 1.5rem; -} - -.m-8 { - margin: 2rem; -} - -.mx-auto { - margin-left: auto; - margin-right: auto; -} - -.my-2 { - margin-top: 0.5rem; - margin-bottom: 0.5rem; -} - -.my-4 { - margin-top: 1rem; - margin-bottom: 1rem; -} - -.my-6 { - margin-top: 1.5rem; - margin-bottom: 1.5rem; -} - -.mb-2 { - margin-bottom: 0.5rem; -} - -.mb-4 { - margin-bottom: 1rem; -} - -.mb-6 { - margin-bottom: 1.5rem; -} - -.mb-8 { - margin-bottom: 2rem; -} - -.mt-2 { - margin-top: 0.5rem; -} - -.mt-4 { - margin-top: 1rem; -} - -.mt-6 { - margin-top: 1.5rem; -} - -.mt-8 { - margin-top: 2rem; -} - -.flex { - display: flex; -} - -.flex-col { - flex-direction: column; -} - -.flex-row { - flex-direction: row; -} - -.items-center { - align-items: center; -} - -.items-start { - align-items: flex-start; -} - -.items-end { - align-items: flex-end; -} - -.justify-center { - justify-content: center; -} - -.justify-between { - justify-content: space-between; -} - -.justify-start { - justify-content: flex-start; -} - -.justify-end { - justify-content: flex-end; -} - -.gap-1 { - gap: 0.25rem; -} - -.gap-2 { - gap: 0.5rem; -} - -.gap-4 { - gap: 1rem; -} - -.gap-6 { - gap: 1.5rem; -} - -.w-full { - width: 100%; -} - -.w-auto { - width: auto; -} - -.h-full { - height: 100%; -} - -.h-auto { - height: auto; -} - -.min-h-screen { - min-height: 100vh; -} - -.max-w-sm { - max-width: 24rem; -} - -.max-w-md { - max-width: 28rem; -} - -.max-w-lg { - max-width: 32rem; -} - -.max-w-xl { - max-width: 36rem; -} - -.max-w-2xl { - max-width: 42rem; -} - -.max-w-3xl { - max-width: 48rem; -} - -.max-w-4xl { - max-width: 56rem; -} - -.max-w-5xl { - max-width: 64rem; -} - -.max-w-6xl { - max-width: 72rem; -} - -.max-w-7xl { - max-width: 80rem; -} - -.hidden { - display: none; -} - -.block { - display: block; -} - -.inline-block { - display: inline-block; -} - -.inline { - display: inline; -} - -.grid { - display: grid; -} - -.grid-cols-1 { - grid-template-columns: repeat(1, minmax(0, 1fr)); -} - -.grid-cols-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)); -} - -.grid-cols-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); -} - -.grid-cols-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); -} - -.relative { - position: relative; -} - -.absolute { - position: absolute; -} - -.fixed { - position: fixed; -} - -.sticky { - position: sticky; -} - -.top-0 { - top: 0; -} - -.right-0 { - right: 0; -} - -.bottom-0 { - bottom: 0; -} - -.left-0 { - left: 0; -} - -.z-10 { - z-index: 10; -} - -.z-20 { - z-index: 20; -} - -.z-30 { - z-index: 30; -} - -.z-40 { - z-index: 40; -} - -.z-50 { - z-index: 50; -} - -.overflow-hidden { - overflow: hidden; -} - -.overflow-auto { - overflow: auto; -} - -.overflow-scroll { - overflow: scroll; -} - -.overflow-x-auto { - overflow-x: auto; -} - -.overflow-y-auto { - overflow-y: auto; -} - -.border { - border-width: 1px; -} - -.border-0 { - border-width: 0px; -} - -.border-2 { - border-width: 2px; -} - -.border-4 { - border-width: 4px; -} - -.border-gray-200 { - border-color: #e5e7eb; -} - -.border-gray-300 { - border-color: #d1d5db; -} - -.border-gray-400 { - border-color: #9ca3af; -} - -.border-orange-500 { - border-color: #ff8000; -} - -.border-orange-600 { - border-color: #e67300; -} - -.border-solid { - border-style: solid; -} - -.border-dashed { - border-style: dashed; -} - -.border-dotted { - border-style: dotted; -} - -.border-none { - border-style: none; -} - -/* Responsive utilities */ -@media (min-width: 640px) { - .sm\:block { - display: block; - } - - .sm\:hidden { - display: none; - } - - .sm\:flex { - display: flex; - } - - .sm\:grid { - display: grid; - } - - .sm\:grid-cols-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .sm\:grid-cols-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } -} - -@media (min-width: 768px) { - .md\:block { - display: block; - } - - .md\:hidden { - display: none; - } - - .md\:flex { - display: flex; - } - - .md\:grid { - display: grid; - } - - .md\:grid-cols-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .md\:grid-cols-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - - .md\:grid-cols-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } -} - -@media (min-width: 1024px) { - .lg\:block { - display: block; - } - - .lg\:hidden { - display: none; - } - - .lg\:flex { - display: flex; - } - - .lg\:grid { - display: grid; - } - - .lg\:grid-cols-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .lg\:grid-cols-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - - .lg\:grid-cols-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } -} - -@media (min-width: 1280px) { - .xl\:block { - display: block; - } - - .xl\:hidden { - display: none; - } - - .xl\:flex { - display: flex; - } - - .xl\:grid { - display: grid; - } - - .xl\:grid-cols-2 { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .xl\:grid-cols-3 { - grid-template-columns: repeat(3, minmax(0, 1fr)); - } - - .xl\:grid-cols-4 { - grid-template-columns: repeat(4, minmax(0, 1fr)); - } -} \ No newline at end of file diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index ac49628..59476e2 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,10 +1,12 @@ -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App.jsx"; -import "./index.css"; - -createRoot(document.getElementById("root")).render( - - - -); +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App.jsx"; +// import reportWebVitals from "./reportWebVitals.js"; + +createRoot(document.getElementById("root")).render( + + + +); + +reportWebVitals(); diff --git a/frontend/src/pages/Dashboard.css b/frontend/src/pages/Dashboard.css deleted file mode 100644 index 0880e5c..0000000 --- a/frontend/src/pages/Dashboard.css +++ /dev/null @@ -1,387 +0,0 @@ -.dashboard { - padding: 2rem 0; -} - -.dashboard-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 2rem; - padding: 2rem; - background: white; - border-radius: 1rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); -} - -.header-content h1 { - font-size: 2.5rem; - font-weight: 700; - color: #1f2937; - margin: 0 0 0.5rem 0; -} - -.header-content p { - font-size: 1.125rem; - color: #6b7280; - margin: 0; -} - -.cta-button { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1.5rem; - background: #ff8000; - color: white; - text-decoration: none; - border-radius: 0.5rem; - font-weight: 500; - transition: all 0.3s ease; - box-shadow: 0 4px 12px rgba(255, 128, 0, 0.3); -} - -.cta-button:hover { - background: #e67300; - transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(255, 128, 0, 0.4); -} - -/* Statistics Grid */ -.stats-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 1.5rem; - margin-bottom: 2rem; -} - -.stat-card { - background: white; - padding: 1.5rem; - border-radius: 1rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); - display: flex; - align-items: center; - gap: 1rem; - transition: all 0.3s ease; -} - -.stat-card:hover { - transform: translateY(-4px); - box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); -} - -.stat-icon { - width: 60px; - height: 60px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 1.5rem; - color: white; -} - -.stat-icon.users { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -} - -.stat-icon.recent { - background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); -} - -.stat-icon.percentage { - background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); -} - -.stat-icon.courses { - background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); -} - -.stat-content h3 { - font-size: 2rem; - font-weight: 700; - color: #1f2937; - margin: 0 0 0.25rem 0; -} - -.stat-content p { - font-size: 0.875rem; - color: #6b7280; - margin: 0; - font-weight: 500; -} - -/* Dashboard Content */ -.dashboard-content { - display: grid; - grid-template-columns: 2fr 1fr; - gap: 2rem; -} - -.content-section { - background: white; - border-radius: 1rem; - padding: 1.5rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); -} - -.section-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1.5rem; - padding-bottom: 1rem; - border-bottom: 1px solid #e5e7eb; -} - -.section-header h2 { - font-size: 1.5rem; - font-weight: 600; - color: #1f2937; - margin: 0; -} - -.view-all-link { - color: #ff8000; - text-decoration: none; - font-weight: 500; - font-size: 0.875rem; - transition: color 0.3s ease; -} - -.view-all-link:hover { - color: #e67300; -} - -/* Students Grid */ -.students-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - gap: 1rem; -} - -.student-card { - display: flex; - align-items: center; - gap: 1rem; - padding: 1rem; - border: 1px solid #e5e7eb; - border-radius: 0.5rem; - transition: all 0.3s ease; -} - -.student-card:hover { - border-color: #ff8000; - box-shadow: 0 4px 12px rgba(255, 128, 0, 0.1); -} - -.student-avatar { - width: 50px; - height: 50px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - color: white; - font-size: 1.25rem; -} - -.student-info h4 { - font-size: 1rem; - font-weight: 600; - color: #1f2937; - margin: 0 0 0.25rem 0; -} - -.student-course { - font-size: 0.875rem; - color: #6b7280; - margin: 0 0 0.25rem 0; -} - -.student-percentage { - font-size: 0.875rem; - font-weight: 600; - color: #ff8000; - margin: 0; -} - -/* Courses List */ -.courses-list { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.course-item { - display: flex; - align-items: center; - gap: 1rem; - padding: 1rem; - border: 1px solid #e5e7eb; - border-radius: 0.5rem; - transition: all 0.3s ease; -} - -.course-item:hover { - border-color: #ff8000; - background: rgba(255, 128, 0, 0.05); -} - -.course-rank { - width: 30px; - height: 30px; - background: #ff8000; - color: white; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-weight: 600; - font-size: 0.875rem; -} - -.course-info h4 { - font-size: 1rem; - font-weight: 600; - color: #1f2937; - margin: 0 0 0.25rem 0; -} - -.course-info p { - font-size: 0.875rem; - color: #6b7280; - margin: 0; -} - -.course-percentage { - margin-left: auto; - font-weight: 600; - color: #ff8000; - font-size: 0.875rem; -} - -/* Empty State */ -.empty-state { - text-align: center; - padding: 3rem 1rem; - color: #6b7280; -} - -.empty-icon { - font-size: 3rem; - color: #d1d5db; - margin-bottom: 1rem; -} - -.empty-state p { - font-size: 1.125rem; - margin-bottom: 1.5rem; -} - -.empty-cta { - display: inline-flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1.5rem; - background: #ff8000; - color: white; - text-decoration: none; - border-radius: 0.5rem; - font-weight: 500; - transition: all 0.3s ease; -} - -.empty-cta:hover { - background: #e67300; - transform: translateY(-2px); -} - -/* Loading Spinner */ -.loading-spinner { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 4rem 2rem; - color: #6b7280; -} - -.spinner { - width: 40px; - height: 40px; - border: 4px solid #e5e7eb; - border-top: 4px solid #ff8000; - border-radius: 50%; - animation: spin 1s linear infinite; - margin-bottom: 1rem; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -/* Responsive Design */ -@media (max-width: 1024px) { - .dashboard-content { - grid-template-columns: 1fr; - } -} - -@media (max-width: 768px) { - .dashboard-header { - flex-direction: column; - gap: 1rem; - text-align: center; - } - - .header-content h1 { - font-size: 2rem; - } - - .stats-grid { - grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); - } - - .students-grid { - grid-template-columns: 1fr; - } - - .stat-card { - padding: 1rem; - } - - .stat-icon { - width: 50px; - height: 50px; - font-size: 1.25rem; - } - - .stat-content h3 { - font-size: 1.5rem; - } -} - -@media (max-width: 480px) { - .dashboard { - padding: 1rem 0; - } - - .dashboard-header { - padding: 1.5rem; - } - - .header-content h1 { - font-size: 1.75rem; - } - - .stats-grid { - grid-template-columns: 1fr; - } - - .content-section { - padding: 1rem; - } -} \ No newline at end of file diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx deleted file mode 100644 index 5612d1b..0000000 --- a/frontend/src/pages/Dashboard.jsx +++ /dev/null @@ -1,187 +0,0 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; -import { useQuery } from '@tanstack/react-query'; -import { FaUsers, FaUserPlus, FaGraduationCap, FaChartLine } from 'react-icons/fa'; -import { fetchUsers } from '../api/userService'; -import { useApp } from '../context/AppContext'; -import './Dashboard.css'; - -const Dashboard = () => { - const { addNotification } = useApp(); - - const { data: users = [], isLoading, error } = useQuery({ - queryKey: ['users'], - queryFn: fetchUsers, - staleTime: 5 * 60 * 1000, // 5 minutes - }); - - // Calculate statistics - const stats = { - totalStudents: users.length, - recentRegistrations: users.slice(-5).length, - averagePercentage: users.length > 0 - ? Math.round(users.reduce((sum, user) => sum + parseFloat(user.percentage || 0), 0) / users.length) - : 0, - topCourses: getTopCourses(users), - }; - - function getTopCourses(users) { - const courseCount = {}; - users.forEach(user => { - courseCount[user.course] = (courseCount[user.course] || 0) + 1; - }); - return Object.entries(courseCount) - .sort(([,a], [,b]) => b - a) - .slice(0, 3) - .map(([course, count]) => ({ course, count })); - } - - const recentStudents = users.slice(-4).reverse(); - - if (isLoading) { - return ( -
-
-

Dashboard

-

Welcome to CLOUDBLITZ Student Management System

-
-
-
-

Loading dashboard data...

-
-
- ); - } - - if (error) { - addNotification({ - type: 'error', - title: 'Error', - message: 'Failed to load dashboard data', - }); - } - - return ( -
-
-
-

Dashboard

-

Welcome to CLOUDBLITZ Student Management System

-
- - - Register New Student - -
- - {/* Statistics Cards */} -
-
-
- -
-
-

{stats.totalStudents}

-

Total Students

-
-
- -
-
- -
-
-

{stats.recentRegistrations}

-

Recent Registrations

-
-
- -
-
- -
-
-

{stats.averagePercentage}%

-

Average Percentage

-
-
- -
-
- -
-
-

{stats.topCourses.length}

-

Active Courses

-
-
-
- -
- {/* Recent Students */} -
-
-

Recent Students

- - View All Students - -
-
- {recentStudents.length > 0 ? ( - recentStudents.map((student) => ( -
-
- -
-
-

{student.name}

-

{student.course}

-

{student.percentage}%

-
-
- )) - ) : ( -
- -

No students registered yet

- - Register First Student - -
- )} -
-
- - {/* Top Courses */} -
-
-

Popular Courses

-
-
- {stats.topCourses.length > 0 ? ( - stats.topCourses.map((course, index) => ( -
-
{index + 1}
-
-

{course.course}

-

{course.count} students

-
-
- {Math.round((course.count / stats.totalStudents) * 100)}% -
-
- )) - ) : ( -
- -

No course data available

-
- )} -
-
-
-
- ); -}; - -export default Dashboard; \ No newline at end of file diff --git a/frontend/src/pages/NotFound.css b/frontend/src/pages/NotFound.css deleted file mode 100644 index 9477b2b..0000000 --- a/frontend/src/pages/NotFound.css +++ /dev/null @@ -1,218 +0,0 @@ -.not-found-page { - min-height: 100vh; - display: flex; - align-items: center; - justify-content: center; - padding: 2rem; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -} - -.not-found-container { - background: white; - border-radius: 1rem; - padding: 3rem; - text-align: center; - max-width: 600px; - box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); - animation: slideIn 0.5s ease-out; -} - -.not-found-icon { - font-size: 4rem; - color: #ff8000; - margin-bottom: 1.5rem; - animation: pulse 2s infinite; -} - -.not-found-title { - font-size: 6rem; - font-weight: 900; - color: #1f2937; - margin: 0 0 0.5rem 0; - line-height: 1; - background: linear-gradient(135deg, #ff8000 0%, #e67300 100%); - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; -} - -.not-found-subtitle { - font-size: 2rem; - font-weight: 700; - color: #374151; - margin: 0 0 1rem 0; -} - -.not-found-message { - font-size: 1.125rem; - color: #6b7280; - line-height: 1.6; - margin-bottom: 2rem; - max-width: 400px; - margin-left: auto; - margin-right: auto; -} - -.not-found-actions { - display: flex; - gap: 1rem; - justify-content: center; - margin-bottom: 2rem; - flex-wrap: wrap; -} - -.not-found-btn { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1.5rem; - border: none; - border-radius: 0.5rem; - font-size: 1rem; - font-weight: 500; - cursor: pointer; - transition: all 0.3s ease; - text-decoration: none; -} - -.not-found-btn.primary { - background: #ff8000; - color: white; - box-shadow: 0 4px 12px rgba(255, 128, 0, 0.3); -} - -.not-found-btn.primary:hover { - background: #e67300; - transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(255, 128, 0, 0.4); -} - -.not-found-btn.secondary { - background: #f3f4f6; - color: #374151; - border: 1px solid #d1d5db; -} - -.not-found-btn.secondary:hover { - background: #e5e7eb; - transform: translateY(-2px); -} - -.not-found-help { - border-top: 1px solid #e5e7eb; - padding-top: 1.5rem; -} - -.not-found-help p { - font-size: 0.875rem; - color: #6b7280; - margin: 0 0 1rem 0; -} - -.help-links { - display: flex; - gap: 1rem; - justify-content: center; - flex-wrap: wrap; -} - -.help-links a { - color: #ff8000; - text-decoration: none; - font-size: 0.875rem; - font-weight: 500; - padding: 0.5rem 1rem; - border: 1px solid #ff8000; - border-radius: 0.5rem; - transition: all 0.3s ease; -} - -.help-links a:hover { - background: #ff8000; - color: white; - transform: translateY(-1px); -} - -/* Animations */ -@keyframes slideIn { - from { - opacity: 0; - transform: translateY(30px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@keyframes pulse { - 0%, 100% { - transform: scale(1); - } - 50% { - transform: scale(1.05); - } -} - -/* Responsive Design */ -@media (max-width: 768px) { - .not-found-page { - padding: 1rem; - } - - .not-found-container { - padding: 2rem; - } - - .not-found-title { - font-size: 4rem; - } - - .not-found-subtitle { - font-size: 1.5rem; - } - - .not-found-message { - font-size: 1rem; - } - - .not-found-actions { - flex-direction: column; - align-items: center; - } - - .not-found-btn { - width: 100%; - max-width: 200px; - justify-content: center; - } - - .help-links { - flex-direction: column; - align-items: center; - } - - .help-links a { - width: 100%; - max-width: 200px; - text-align: center; - } -} - -@media (max-width: 480px) { - .not-found-container { - padding: 1.5rem; - } - - .not-found-title { - font-size: 3rem; - } - - .not-found-subtitle { - font-size: 1.25rem; - } - - .not-found-icon { - font-size: 3rem; - } -} \ No newline at end of file diff --git a/frontend/src/pages/NotFound.jsx b/frontend/src/pages/NotFound.jsx deleted file mode 100644 index 4f8cae0..0000000 --- a/frontend/src/pages/NotFound.jsx +++ /dev/null @@ -1,49 +0,0 @@ -import React from 'react'; -import { Link } from 'react-router-dom'; -import { FaHome, FaArrowLeft, FaExclamationTriangle } from 'react-icons/fa'; -import './NotFound.css'; - -const NotFound = () => { - return ( -
-
-
- -
- -

404

-

Page Not Found

- -

- Oops! The page you're looking for doesn't exist. It might have been moved, deleted, or you entered the wrong URL. -

- -
- - - Go to Dashboard - - - -
- -
-

Need help? Try these links:

-
- Register Student - View Students - Dashboard -
-
-
-
- ); -}; - -export default NotFound; \ No newline at end of file diff --git a/frontend/src/pages/StudentList.css b/frontend/src/pages/StudentList.css deleted file mode 100644 index 4dca567..0000000 --- a/frontend/src/pages/StudentList.css +++ /dev/null @@ -1,576 +0,0 @@ -.student-list-page { - padding: 2rem 0; -} - -.list-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 2rem; - padding: 2rem; - background: white; - border-radius: 1rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); -} - -.header-content { - display: flex; - align-items: center; - gap: 1rem; -} - -.header-icon { - width: 60px; - height: 60px; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - color: white; - font-size: 1.5rem; -} - -.header-content h1 { - font-size: 2rem; - font-weight: 700; - color: #1f2937; - margin: 0 0 0.5rem 0; -} - -.header-content p { - font-size: 1.125rem; - color: #6b7280; - margin: 0; -} - -.add-button { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1.5rem; - background: #ff8000; - color: white; - text-decoration: none; - border-radius: 0.5rem; - font-weight: 500; - transition: all 0.3s ease; - box-shadow: 0 4px 12px rgba(255, 128, 0, 0.3); -} - -.add-button:hover { - background: #e67300; - transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(255, 128, 0, 0.4); -} - -/* Filters Section */ -.filters-section { - background: white; - padding: 1.5rem; - border-radius: 1rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); - margin-bottom: 1.5rem; -} - -.search-box { - position: relative; - margin-bottom: 1rem; -} - -.search-icon { - position: absolute; - left: 1rem; - top: 50%; - transform: translateY(-50%); - color: #9ca3af; - font-size: 1rem; -} - -.search-input { - width: 100%; - padding: 0.75rem 1rem 0.75rem 2.5rem; - border: 2px solid #e5e7eb; - border-radius: 0.5rem; - font-size: 1rem; - transition: all 0.3s ease; -} - -.search-input:focus { - outline: none; - border-color: #ff8000; - box-shadow: 0 0 0 3px rgba(255, 128, 0, 0.1); -} - -.filter-controls { - display: flex; - gap: 1rem; - align-items: center; - flex-wrap: wrap; -} - -.filter-group { - display: flex; - align-items: center; - gap: 0.5rem; -} - -.filter-group label { - font-weight: 500; - color: #374151; - font-size: 0.875rem; -} - -.filter-select { - padding: 0.5rem; - border: 2px solid #e5e7eb; - border-radius: 0.5rem; - font-size: 0.875rem; - background: white; - cursor: pointer; - transition: all 0.3s ease; -} - -.filter-select:focus { - outline: none; - border-color: #ff8000; - box-shadow: 0 0 0 3px rgba(255, 128, 0, 0.1); -} - -.clear-filters { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.5rem 1rem; - background: #f3f4f6; - color: #374151; - border: 1px solid #d1d5db; - border-radius: 0.5rem; - font-size: 0.875rem; - cursor: pointer; - transition: all 0.3s ease; -} - -.clear-filters:hover { - background: #e5e7eb; - transform: translateY(-1px); -} - -/* Results Summary */ -.results-summary { - background: white; - padding: 1rem 1.5rem; - border-radius: 0.5rem; - margin-bottom: 1rem; - box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); -} - -.results-summary p { - margin: 0; - color: #6b7280; - font-size: 0.875rem; -} - -/* Table Container */ -.table-container { - background: white; - border-radius: 1rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); - overflow: hidden; - margin-bottom: 1.5rem; -} - -.students-table { - width: 100%; - border-collapse: collapse; -} - -.students-table th { - background: #f9fafb; - padding: 1rem; - text-align: left; - font-weight: 600; - color: #374151; - border-bottom: 1px solid #e5e7eb; - font-size: 0.875rem; - text-transform: uppercase; - letter-spacing: 0.05em; -} - -.students-table th.sortable { - cursor: pointer; - user-select: none; - transition: background-color 0.3s ease; -} - -.students-table th.sortable:hover { - background: #f3f4f6; -} - -.sort-indicator { - margin-left: 0.5rem; - color: #ff8000; - font-weight: bold; -} - -.students-table td { - padding: 1rem; - border-bottom: 1px solid #f3f4f6; - vertical-align: middle; -} - -.students-table tr:hover { - background: #f9fafb; -} - -.student-name { - font-weight: 600; - color: #1f2937; -} - -.student-email { - color: #6b7280; - font-size: 0.875rem; -} - -.course-badge { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - padding: 0.25rem 0.75rem; - border-radius: 1rem; - font-size: 0.75rem; - font-weight: 500; - text-transform: uppercase; - letter-spacing: 0.05em; -} - -.percentage-badge { - padding: 0.25rem 0.75rem; - border-radius: 1rem; - font-size: 0.75rem; - font-weight: 600; - text-align: center; - min-width: 60px; - display: inline-block; -} - -.percentage-badge.excellent { - background: #dcfce7; - color: #166534; -} - -.percentage-badge.good { - background: #dbeafe; - color: #1e40af; -} - -.percentage-badge.average { - background: #fef3c7; - color: #92400e; -} - -.percentage-badge.below-average { - background: #fed7aa; - color: #c2410c; -} - -.percentage-badge.poor { - background: #fee2e2; - color: #991b1b; -} - -/* Actions */ -.actions { - display: flex; - gap: 0.5rem; - justify-content: center; -} - -.action-btn { - width: 32px; - height: 32px; - border: none; - border-radius: 0.375rem; - display: flex; - align-items: center; - justify-content: center; - cursor: pointer; - transition: all 0.3s ease; - font-size: 0.875rem; -} - -.action-btn.view { - background: #dbeafe; - color: #1e40af; -} - -.action-btn.view:hover { - background: #bfdbfe; - transform: translateY(-1px); -} - -.action-btn.edit { - background: #fef3c7; - color: #92400e; -} - -.action-btn.edit:hover { - background: #fde68a; - transform: translateY(-1px); -} - -.action-btn.delete { - background: #fee2e2; - color: #991b1b; -} - -.action-btn.delete:hover:not(:disabled) { - background: #fecaca; - transform: translateY(-1px); -} - -.action-btn:disabled { - opacity: 0.5; - cursor: not-allowed; - transform: none; -} - -/* Empty State */ -.no-data { - text-align: center; - padding: 3rem 1rem; -} - -.empty-state { - display: flex; - flex-direction: column; - align-items: center; - gap: 1rem; - color: #6b7280; -} - -.empty-icon { - font-size: 3rem; - color: #d1d5db; -} - -.empty-state p { - font-size: 1.125rem; - margin: 0; -} - -.clear-filters-btn, -.add-first-btn { - padding: 0.5rem 1rem; - border: none; - border-radius: 0.5rem; - font-size: 0.875rem; - cursor: pointer; - transition: all 0.3s ease; - text-decoration: none; - display: inline-block; -} - -.clear-filters-btn { - background: #f3f4f6; - color: #374151; - border: 1px solid #d1d5db; -} - -.clear-filters-btn:hover { - background: #e5e7eb; -} - -.add-first-btn { - background: #ff8000; - color: white; -} - -.add-first-btn:hover { - background: #e67300; - transform: translateY(-1px); -} - -/* Pagination */ -.pagination { - display: flex; - justify-content: center; - align-items: center; - gap: 1rem; - margin-top: 2rem; -} - -.pagination-btn { - padding: 0.5rem 1rem; - border: 1px solid #d1d5db; - background: white; - color: #374151; - border-radius: 0.5rem; - cursor: pointer; - transition: all 0.3s ease; - font-size: 0.875rem; -} - -.pagination-btn:hover:not(:disabled) { - background: #f3f4f6; - border-color: #9ca3af; -} - -.pagination-btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.page-numbers { - display: flex; - gap: 0.25rem; -} - -.page-btn { - width: 40px; - height: 40px; - border: 1px solid #d1d5db; - background: white; - color: #374151; - border-radius: 0.5rem; - cursor: pointer; - transition: all 0.3s ease; - font-size: 0.875rem; - font-weight: 500; -} - -.page-btn:hover { - background: #f3f4f6; - border-color: #9ca3af; -} - -.page-btn.active { - background: #ff8000; - color: white; - border-color: #ff8000; -} - -.page-btn.active:hover { - background: #e67300; -} - -/* Loading and Error States */ -.loading-spinner, -.error-state { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - padding: 4rem 2rem; - color: #6b7280; -} - -.spinner { - width: 40px; - height: 40px; - border: 4px solid #e5e7eb; - border-top: 4px solid #ff8000; - border-radius: 50%; - animation: spin 1s linear infinite; - margin-bottom: 1rem; -} - -.error-icon { - font-size: 3rem; - color: #ef4444; - margin-bottom: 1rem; -} - -.error-state h2 { - color: #1f2937; - margin: 0 0 0.5rem 0; -} - -.error-state p { - margin: 0; - text-align: center; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -/* Responsive Design */ -@media (max-width: 1024px) { - .students-table { - font-size: 0.875rem; - } - - .students-table th, - .students-table td { - padding: 0.75rem 0.5rem; - } -} - -@media (max-width: 768px) { - .list-header { - flex-direction: column; - gap: 1rem; - text-align: center; - } - - .header-content h1 { - font-size: 1.75rem; - } - - .filter-controls { - flex-direction: column; - align-items: stretch; - } - - .filter-group { - flex-direction: column; - align-items: stretch; - } - - .table-container { - overflow-x: auto; - } - - .students-table { - min-width: 800px; - } - - .pagination { - flex-direction: column; - gap: 0.5rem; - } - - .page-numbers { - order: -1; - } -} - -@media (max-width: 480px) { - .student-list-page { - padding: 1rem 0; - } - - .list-header { - padding: 1.5rem; - } - - .header-content h1 { - font-size: 1.5rem; - } - - .filters-section { - padding: 1rem; - } - - .search-input { - font-size: 0.875rem; - } - - .action-btn { - width: 28px; - height: 28px; - font-size: 0.75rem; - } -} \ No newline at end of file diff --git a/frontend/src/pages/StudentList.jsx b/frontend/src/pages/StudentList.jsx deleted file mode 100644 index 89163ab..0000000 --- a/frontend/src/pages/StudentList.jsx +++ /dev/null @@ -1,360 +0,0 @@ -import React, { useState, useMemo } from 'react'; -import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { Link } from 'react-router-dom'; -import { FaUsers, FaSearch, FaFilter, FaTrash, FaEdit, FaEye, FaPlus } from 'react-icons/fa'; -import { fetchUsers, deleteUser } from '../api/userService'; -import { useApp } from '../context/AppContext'; -import toast from 'react-hot-toast'; -import './StudentList.css'; - -const StudentList = () => { - const queryClient = useQueryClient(); - const { addNotification } = useApp(); - - const [searchTerm, setSearchTerm] = useState(''); - const [filterCourse, setFilterCourse] = useState(''); - const [sortBy, setSortBy] = useState('name'); - const [sortOrder, setSortOrder] = useState('asc'); - const [currentPage, setCurrentPage] = useState(1); - const [itemsPerPage] = useState(10); - - // Fetch users - const { data: users = [], isLoading, error } = useQuery({ - queryKey: ['users'], - queryFn: fetchUsers, - staleTime: 5 * 60 * 1000, - }); - - // Delete user mutation - const deleteMutation = useMutation({ - mutationFn: deleteUser, - onSuccess: () => { - toast.success('Student deleted successfully'); - addNotification({ - type: 'success', - title: 'Success', - message: 'Student has been deleted successfully', - }); - queryClient.invalidateQueries({ queryKey: ['users'] }); - }, - onError: () => { - toast.error('Failed to delete student'); - addNotification({ - type: 'error', - title: 'Error', - message: 'Failed to delete student. Please try again.', - }); - }, - }); - - // Filter and sort users - const filteredAndSortedUsers = useMemo(() => { - let filtered = users.filter(user => { - const matchesSearch = user.name.toLowerCase().includes(searchTerm.toLowerCase()) || - user.email.toLowerCase().includes(searchTerm.toLowerCase()) || - user.course.toLowerCase().includes(searchTerm.toLowerCase()); - const matchesCourse = !filterCourse || user.course === filterCourse; - return matchesSearch && matchesCourse; - }); - - filtered.sort((a, b) => { - let aValue = a[sortBy]; - let bValue = b[sortBy]; - - if (sortBy === 'percentage') { - aValue = parseFloat(aValue) || 0; - bValue = parseFloat(bValue) || 0; - } else { - aValue = aValue?.toLowerCase() || ''; - bValue = bValue?.toLowerCase() || ''; - } - - if (sortOrder === 'asc') { - return aValue > bValue ? 1 : -1; - } else { - return aValue < bValue ? 1 : -1; - } - }); - - return filtered; - }, [users, searchTerm, filterCourse, sortBy, sortOrder]); - - // Pagination - const totalPages = Math.ceil(filteredAndSortedUsers.length / itemsPerPage); - const startIndex = (currentPage - 1) * itemsPerPage; - const paginatedUsers = filteredAndSortedUsers.slice(startIndex, startIndex + itemsPerPage); - - // Get unique courses for filter - const uniqueCourses = useMemo(() => { - const courses = [...new Set(users.map(user => user.course))]; - return courses.sort(); - }, [users]); - - // Handle delete - const handleDelete = (userId, userName) => { - if (window.confirm(`Are you sure you want to delete ${userName}?`)) { - deleteMutation.mutate(userId); - } - }; - - // Handle sort - const handleSort = (field) => { - if (sortBy === field) { - setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); - } else { - setSortBy(field); - setSortOrder('asc'); - } - }; - - // Clear filters - const clearFilters = () => { - setSearchTerm(''); - setFilterCourse(''); - setSortBy('name'); - setSortOrder('asc'); - setCurrentPage(1); - }; - - if (isLoading) { - return ( -
-
-
-

Loading students...

-
-
- ); - } - - if (error) { - return ( -
-
- -

Error Loading Students

-

Failed to load student data. Please try again.

-
-
- ); - } - - return ( -
-
-
-
- -
-
-

Student List

-

Manage and view all registered students

-
-
- - - Add Student - -
- - {/* Filters and Search */} -
-
- - setSearchTerm(e.target.value)} - className="search-input" - /> -
- -
-
- - -
- - -
-
- - {/* Results Summary */} -
-

- Showing {paginatedUsers.length} of {filteredAndSortedUsers.length} students - {searchTerm && ` matching "${searchTerm}"`} - {filterCourse && ` in ${filterCourse}`} -

-
- - {/* Students Table */} -
- - - - - - - - - - - - - - - {paginatedUsers.length > 0 ? ( - paginatedUsers.map((student) => ( - - - - - - - - - - - )) - ) : ( - - - - )} - -
handleSort('name')} className="sortable"> - Name - {sortBy === 'name' && ( - - {sortOrder === 'asc' ? 'โ†‘' : 'โ†“'} - - )} - handleSort('email')} className="sortable"> - Email - {sortBy === 'email' && ( - - {sortOrder === 'asc' ? 'โ†‘' : 'โ†“'} - - )} - handleSort('course')} className="sortable"> - Course - {sortBy === 'course' && ( - - {sortOrder === 'asc' ? 'โ†‘' : 'โ†“'} - - )} - Education handleSort('percentage')} className="sortable"> - Percentage - {sortBy === 'percentage' && ( - - {sortOrder === 'asc' ? 'โ†‘' : 'โ†“'} - - )} - BranchMobileActions
{student.name}{student.email} - {student.course} - {student.studentClass} - - {student.percentage}% - - {student.branch}{student.mobileNumber} - - - -
-
- -

No students found

- {searchTerm || filterCourse ? ( - - ) : ( - - Add First Student - - )} -
-
-
- - {/* Pagination */} - {totalPages > 1 && ( -
- - -
- {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => ( - - ))} -
- - -
- )} -
- ); -}; - -// Helper function to get percentage class -const getPercentageClass = (percentage) => { - const num = parseFloat(percentage); - if (num >= 90) return 'excellent'; - if (num >= 80) return 'good'; - if (num >= 70) return 'average'; - if (num >= 60) return 'below-average'; - return 'poor'; -}; - -export default StudentList; \ No newline at end of file diff --git a/frontend/src/pages/StudentRegistration.css b/frontend/src/pages/StudentRegistration.css deleted file mode 100644 index a4cb5d0..0000000 --- a/frontend/src/pages/StudentRegistration.css +++ /dev/null @@ -1,299 +0,0 @@ -.registration-page { - padding: 2rem 0; -} - -.registration-container { - max-width: 800px; - margin: 0 auto; - background: white; - border-radius: 1rem; - box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); - overflow: hidden; -} - -.registration-header { - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - color: white; - padding: 2rem; - display: flex; - align-items: center; - gap: 1rem; -} - -.header-icon { - width: 60px; - height: 60px; - background: rgba(255, 255, 255, 0.2); - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; - font-size: 1.5rem; -} - -.header-content h1 { - font-size: 2rem; - font-weight: 700; - margin: 0 0 0.5rem 0; -} - -.header-content p { - font-size: 1.125rem; - margin: 0; - opacity: 0.9; -} - -.registration-form { - padding: 2rem; -} - -.form-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 1.5rem; - margin-bottom: 2rem; -} - -.form-group { - display: flex; - flex-direction: column; -} - -.form-group.full-width { - grid-column: 1 / -1; -} - -.form-group label { - font-weight: 600; - color: #374151; - margin-bottom: 0.5rem; - font-size: 0.875rem; -} - -.form-input { - padding: 0.75rem; - border: 2px solid #e5e7eb; - border-radius: 0.5rem; - font-size: 1rem; - transition: all 0.3s ease; - background: white; -} - -.form-input:focus { - outline: none; - border-color: #ff8000; - box-shadow: 0 0 0 3px rgba(255, 128, 0, 0.1); -} - -.form-input.error { - border-color: #ef4444; - box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1); -} - -.form-input::placeholder { - color: #9ca3af; -} - -select.form-input { - cursor: pointer; - background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e"); - background-position: right 0.5rem center; - background-repeat: no-repeat; - background-size: 1.5em 1.5em; - padding-right: 2.5rem; - appearance: none; -} - -.error-message { - color: #ef4444; - font-size: 0.875rem; - margin-top: 0.25rem; - font-weight: 500; -} - -.form-actions { - display: flex; - gap: 1rem; - justify-content: flex-end; - padding-top: 1rem; - border-top: 1px solid #e5e7eb; -} - -.btn-primary, -.btn-secondary { - display: flex; - align-items: center; - gap: 0.5rem; - padding: 0.75rem 1.5rem; - border: none; - border-radius: 0.5rem; - font-size: 1rem; - font-weight: 500; - cursor: pointer; - transition: all 0.3s ease; - text-decoration: none; -} - -.btn-primary { - background: #ff8000; - color: white; - box-shadow: 0 4px 12px rgba(255, 128, 0, 0.3); -} - -.btn-primary:hover:not(:disabled) { - background: #e67300; - transform: translateY(-2px); - box-shadow: 0 6px 20px rgba(255, 128, 0, 0.4); -} - -.btn-primary:disabled { - background: #9ca3af; - cursor: not-allowed; - transform: none; - box-shadow: none; -} - -.btn-secondary { - background: #f3f4f6; - color: #374151; - border: 1px solid #d1d5db; -} - -.btn-secondary:hover:not(:disabled) { - background: #e5e7eb; - transform: translateY(-2px); -} - -.btn-secondary:disabled { - background: #f3f4f6; - color: #9ca3af; - cursor: not-allowed; - transform: none; -} - -.spinner { - animation: spin 1s linear infinite; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -/* Responsive Design */ -@media (max-width: 768px) { - .registration-page { - padding: 1rem 0; - } - - .registration-header { - padding: 1.5rem; - flex-direction: column; - text-align: center; - } - - .header-content h1 { - font-size: 1.75rem; - } - - .registration-form { - padding: 1.5rem; - } - - .form-grid { - grid-template-columns: 1fr; - gap: 1rem; - } - - .form-actions { - flex-direction: column; - } - - .btn-primary, - .btn-secondary { - width: 100%; - justify-content: center; - } -} - -@media (max-width: 480px) { - .registration-header { - padding: 1rem; - } - - .header-content h1 { - font-size: 1.5rem; - } - - .header-content p { - font-size: 1rem; - } - - .registration-form { - padding: 1rem; - } - - .form-input { - padding: 0.625rem; - font-size: 0.875rem; - } -} - -/* Focus states for accessibility */ -.form-input:focus { - outline: 2px solid #ff8000; - outline-offset: 2px; -} - -.btn-primary:focus, -.btn-secondary:focus { - outline: 2px solid #ff8000; - outline-offset: 2px; -} - -/* Animation for form appearance */ -.registration-container { - animation: slideUp 0.5s ease-out; -} - -@keyframes slideUp { - from { - opacity: 0; - transform: translateY(30px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -/* Loading state styles */ -.form-input:disabled { - background: #f9fafb; - cursor: not-allowed; -} - -/* Success state for form inputs */ -.form-input.success { - border-color: #10b981; - box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1); -} - -/* Custom scrollbar for select dropdowns */ -select.form-input::-webkit-scrollbar { - width: 8px; -} - -select.form-input::-webkit-scrollbar-track { - background: #f1f5f9; - border-radius: 4px; -} - -select.form-input::-webkit-scrollbar-thumb { - background: #cbd5e1; - border-radius: 4px; -} - -select.form-input::-webkit-scrollbar-thumb:hover { - background: #94a3b8; -} \ No newline at end of file diff --git a/frontend/src/pages/StudentRegistration.jsx b/frontend/src/pages/StudentRegistration.jsx deleted file mode 100644 index d5c7c0a..0000000 --- a/frontend/src/pages/StudentRegistration.jsx +++ /dev/null @@ -1,343 +0,0 @@ -import React, { useState } from 'react'; -import { useMutation, useQueryClient } from '@tanstack/react-query'; -import { useNavigate } from 'react-router-dom'; -import { FaUserPlus, FaSpinner, FaCheck } from 'react-icons/fa'; -import { registerUser } from '../api/userService'; -import { useApp } from '../context/AppContext'; -import toast from 'react-hot-toast'; -import './StudentRegistration.css'; - -const StudentRegistration = () => { - const navigate = useNavigate(); - const queryClient = useQueryClient(); - const { addNotification } = useApp(); - - const [formData, setFormData] = useState({ - name: '', - email: '', - course: '', - studentClass: '', - percentage: '', - branch: '', - mobileNumber: '', - }); - - const [errors, setErrors] = useState({}); - const [isSubmitting, setIsSubmitting] = useState(false); - - // Course options - const courseOptions = [ - 'Computer Science', - 'Information Technology', - 'Electronics & Communication', - 'Mechanical Engineering', - 'Civil Engineering', - 'Electrical Engineering', - 'Business Administration', - 'Data Science', - 'Artificial Intelligence', - 'Cybersecurity', - ]; - - // Branch options - const branchOptions = [ - 'Computer Science', - 'Information Technology', - 'Electronics', - 'Mechanical', - 'Civil', - 'Electrical', - 'Business', - 'Data Analytics', - 'AI/ML', - 'Network Security', - ]; - - // Form validation - const validateForm = () => { - const newErrors = {}; - - if (!formData.name.trim()) { - newErrors.name = 'Name is required'; - } else if (formData.name.length < 2) { - newErrors.name = 'Name must be at least 2 characters'; - } - - if (!formData.email.trim()) { - newErrors.email = 'Email is required'; - } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { - newErrors.email = 'Please enter a valid email address'; - } - - if (!formData.course) { - newErrors.course = 'Course is required'; - } - - if (!formData.studentClass.trim()) { - newErrors.studentClass = 'Highest Education is required'; - } - - if (!formData.percentage) { - newErrors.percentage = 'Percentage is required'; - } else if (parseFloat(formData.percentage) < 0 || parseFloat(formData.percentage) > 100) { - newErrors.percentage = 'Percentage must be between 0 and 100'; - } - - if (!formData.branch) { - newErrors.branch = 'Branch/Stream is required'; - } - - if (!formData.mobileNumber) { - newErrors.mobileNumber = 'Mobile number is required'; - } else if (!/^\d{10}$/.test(formData.mobileNumber)) { - newErrors.mobileNumber = 'Please enter a valid 10-digit mobile number'; - } - - setErrors(newErrors); - return Object.keys(newErrors).length === 0; - }; - - // Handle input changes - const handleInputChange = (e) => { - const { name, value } = e.target; - setFormData(prev => ({ - ...prev, - [name]: value - })); - - // Clear error when user starts typing - if (errors[name]) { - setErrors(prev => ({ - ...prev, - [name]: '' - })); - } - }; - - // Registration mutation - const registrationMutation = useMutation({ - mutationFn: registerUser, - onSuccess: (data) => { - setIsSubmitting(false); - toast.success('Student registered successfully!'); - addNotification({ - type: 'success', - title: 'Success', - message: 'Student has been registered successfully', - }); - - // Invalidate and refetch users - queryClient.invalidateQueries({ queryKey: ['users'] }); - - // Reset form - setFormData({ - name: '', - email: '', - course: '', - studentClass: '', - percentage: '', - branch: '', - mobileNumber: '', - }); - - // Navigate to students list - setTimeout(() => { - navigate('/students'); - }, 1500); - }, - onError: (error) => { - setIsSubmitting(false); - toast.error('Failed to register student. Please try again.'); - addNotification({ - type: 'error', - title: 'Error', - message: 'Failed to register student. Please try again.', - }); - }, - }); - - // Handle form submission - const handleSubmit = async (e) => { - e.preventDefault(); - - if (!validateForm()) { - toast.error('Please fix the errors in the form'); - return; - } - - setIsSubmitting(true); - registrationMutation.mutate(formData); - }; - - return ( -
-
-
-
- -
-
-

Student Registration

-

Register a new student in the CLOUDBLITZ system

-
-
- -
-
- {/* Name Field */} -
- - - {errors.name && {errors.name}} -
- - {/* Email Field */} -
- - - {errors.email && {errors.email}} -
- - {/* Course Field */} -
- - - {errors.course && {errors.course}} -
- - {/* Highest Education Field */} -
- - - {errors.studentClass && {errors.studentClass}} -
- - {/* Percentage Field */} -
- - - {errors.percentage && {errors.percentage}} -
- - {/* Branch Field */} -
- - - {errors.branch && {errors.branch}} -
- - {/* Mobile Number Field */} -
- - - {errors.mobileNumber && {errors.mobileNumber}} -
-
- -
- - -
-
-
-
- ); -}; - -export default StudentRegistration; \ No newline at end of file diff --git a/frontend/src/utils/config.js b/frontend/src/utils/config.js deleted file mode 100644 index ad3a91c..0000000 --- a/frontend/src/utils/config.js +++ /dev/null @@ -1,20 +0,0 @@ -// Utility to get configuration values from build-time environment variables -export const getConfig = (key, defaultValue = '') => { - // Use build-time environment variables - if (import.meta.env[key]) { - return import.meta.env[key]; - } - - // Return default value - return defaultValue; -}; - -// Specific getters for common config values -export const getApiUrl = () => { - const apiUrl = getConfig('VITE_API_URL', 'http://localhost:8080/api'); - console.log('API URL:', apiUrl); // Debug log - return apiUrl; -}; - -export const getApiBaseUrl = () => getConfig('VITE_API_BASE_URL', 'http://localhost:8080'); -export const getAppTitle = () => getConfig('VITE_APP_TITLE', 'EasyCRUD Student Registration'); \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 8b0f57b..ea73791 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,7 +1,7 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [react()], -}) +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/setup/docker/DOCKER_SETUP.md b/setup/docker/DOCKER_SETUP.md deleted file mode 100644 index fc9131a..0000000 --- a/setup/docker/DOCKER_SETUP.md +++ /dev/null @@ -1,353 +0,0 @@ -# Docker Setup Guide for EasyCRUD - -This guide explains how to run the entire EasyCRUD application stack using Docker Compose. - -## ๐Ÿณ Overview - -The Docker setup includes: -- **MariaDB Database** - Persistent data storage -- **Spring Boot Backend** - REST API server -- **React Frontend** - User interface -- **Nginx Reverse Proxy** - Load balancer and SSL termination (optional) - -## ๐Ÿ“‹ Prerequisites - -- Docker Engine 20.10+ -- Docker Compose 2.0+ -- At least 4GB RAM available -- Ports 80, 3000, 8080, 3306 available - -## ๐Ÿš€ Quick Start - -### 1. Clone and Navigate -```bash -git clone -cd EasyCRUD -``` - -### 2. Start All Services -```bash -# Start all services in detached mode -docker-compose up -d - -# Or start with logs visible -docker-compose up -``` - -### 3. Access the Application -- **Frontend**: http://localhost:3000 -- **Backend API**: http://localhost:8080 -- **Database**: localhost:3306 -- **Nginx Proxy**: http://localhost:80 - -## ๐Ÿ“ Docker Files Structure - -``` -EasyCRUD/ -โ”œโ”€โ”€ docker-compose.yml # Main orchestration file -โ”œโ”€โ”€ backend/ -โ”‚ โ”œโ”€โ”€ Dockerfile # Backend container build -โ”‚ โ”œโ”€โ”€ .dockerignore # Backend build exclusions -โ”‚ โ””โ”€โ”€ database_schema.sql # Database initialization -โ”œโ”€โ”€ frontend/ -โ”‚ โ”œโ”€โ”€ Dockerfile # Frontend container build -โ”‚ โ”œโ”€โ”€ .dockerignore # Frontend build exclusions -โ”‚ โ””โ”€โ”€ nginx.conf # Frontend nginx config -โ””โ”€โ”€ nginx/ - โ””โ”€โ”€ nginx.conf # Main reverse proxy config -``` - -## ๐Ÿ”ง Service Configuration - -### Database (MariaDB) -- **Image**: mariadb:10.11 -- **Port**: 3306 -- **Database**: student_db -- **User**: easycrud_user -- **Password**: easycrud_password -- **Root Password**: rootpassword -- **Volume**: db_data (persistent storage) -- **Schema**: Auto-imported from database_schema.sql - -### Backend (Spring Boot) -- **Port**: 8080 -- **Database Connection**: jdbc:mariadb://database:3306/student_db -- **Health Check**: /actuator/health -- **Dependencies**: Database service - -### Frontend (React + Nginx) -- **Port**: 3000 -- **Build**: Multi-stage with Node.js and Nginx -- **API Proxy**: /api/* โ†’ backend:8080 -- **Health Check**: /health -- **Dependencies**: Backend service - -### Nginx Reverse Proxy (Optional) -- **Ports**: 80 (HTTP), 443 (HTTPS) -- **Load Balancing**: Frontend and Backend -- **Rate Limiting**: API endpoints -- **SSL**: Configured for production - -## ๐Ÿ› ๏ธ Docker Commands - -### Basic Operations -```bash -# Start services -docker-compose up -d - -# View logs -docker-compose logs -f - -# View specific service logs -docker-compose logs -f backend -docker-compose logs -f frontend -docker-compose logs -f database - -# Stop services -docker-compose down - -# Stop and remove volumes -docker-compose down -v - -# Rebuild and start -docker-compose up --build -d - -# View running containers -docker-compose ps -``` - -### Development Commands -```bash -# Rebuild specific service -docker-compose build backend - -# Restart specific service -docker-compose restart backend - -# Execute commands in running containers -docker-compose exec backend sh -docker-compose exec database mysql -u root -p - -# View container resources -docker stats -``` - -### Database Operations -```bash -# Connect to database -docker-compose exec database mysql -u easycrud_user -p student_db - -# Backup database -docker-compose exec database mysqldump -u root -prootpassword student_db > backup.sql - -# Restore database -docker-compose exec -T database mysql -u root -prootpassword student_db < backup.sql - -# View database logs -docker-compose logs database -``` - -## ๐Ÿ” Health Checks - -All services include health checks: - -```bash -# Check service health -docker-compose ps - -# Manual health checks -curl http://localhost:3000/health # Frontend -curl http://localhost:8080/actuator/health # Backend -curl http://localhost/health # Nginx -``` - -## ๐Ÿ“Š Monitoring - -### View Resource Usage -```bash -# Container statistics -docker stats - -# Service status -docker-compose ps - -# Log monitoring -docker-compose logs -f --tail=100 -``` - -### Performance Monitoring -```bash -# Check database performance -docker-compose exec database mysql -u root -p -e "SHOW PROCESSLIST;" - -# Check nginx access logs -docker-compose exec nginx tail -f /var/log/nginx/access.log - -# Check application logs -docker-compose logs -f backend -``` - -## ๐Ÿ”’ Security Configuration - -### Environment Variables -Create a `.env` file for production: - -```env -# Database -MYSQL_ROOT_PASSWORD=your_secure_root_password -MYSQL_DATABASE=student_db -MYSQL_USER=your_db_user -MYSQL_PASSWORD=your_secure_password - -# Backend -SPRING_PROFILES_ACTIVE=prod -SPRING_DATASOURCE_URL=jdbc:mariadb://database:3306/student_db -SPRING_DATASOURCE_USERNAME=${MYSQL_USER} -SPRING_DATASOURCE_PASSWORD=${MYSQL_PASSWORD} - -# Frontend -VITE_API_BASE_URL=https://your-domain.com -``` - -### SSL Configuration -For production HTTPS: - -1. **Generate SSL certificates** -2. **Update nginx/nginx.conf** -3. **Uncomment HTTPS server block** -4. **Set proper domain name** - -## ๐Ÿšจ Troubleshooting - -### Common Issues - -#### 1. Port Conflicts -```bash -# Check port usage -netstat -tulpn | grep :8080 -lsof -i :8080 - -# Change ports in docker-compose.yml -ports: - - "8081:8080" # Use different host port -``` - -#### 2. Database Connection Issues -```bash -# Check database status -docker-compose logs database - -# Test database connection -docker-compose exec backend curl -f http://localhost:8080/actuator/health - -# Reset database -docker-compose down -v -docker-compose up -d -``` - -#### 3. Frontend Build Issues -```bash -# Rebuild frontend -docker-compose build --no-cache frontend -docker-compose up -d frontend - -# Check build logs -docker-compose logs frontend -``` - -#### 4. Memory Issues -```bash -# Check available memory -free -h - -# Increase Docker memory limit -# In Docker Desktop: Settings โ†’ Resources โ†’ Memory -``` - -### Debug Commands -```bash -# Inspect container -docker-compose exec backend sh -docker-compose exec frontend sh - -# View container details -docker inspect easycrud-backend - -# Check network connectivity -docker-compose exec backend ping database -docker-compose exec frontend ping backend -``` - -## ๐Ÿ“ˆ Production Deployment - -### 1. Environment Setup -```bash -# Create production environment file -cp .env.example .env.prod - -# Edit production variables -nano .env.prod -``` - -### 2. SSL Certificate Setup -```bash -# Create SSL directory -mkdir -p nginx/ssl - -# Add your certificates -cp your-cert.pem nginx/ssl/cert.pem -cp your-key.pem nginx/ssl/key.pem -``` - -### 3. Production Deployment -```bash -# Use production compose file -docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d - -# Or set environment -export COMPOSE_ENV=production -docker-compose up -d -``` - -### 4. Backup Strategy -```bash -# Create backup script -#!/bin/bash -DATE=$(date +%Y%m%d_%H%M%S) -docker-compose exec database mysqldump -u root -prootpassword student_db > backup_$DATE.sql -``` - -## ๐Ÿ”„ CI/CD Integration - -### GitHub Actions Example -```yaml -name: Deploy to Production -on: - push: - branches: [main] - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Deploy with Docker Compose - run: | - docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d -``` - -## ๐Ÿ“š Additional Resources - -- [Docker Compose Documentation](https://docs.docker.com/compose/) -- [MariaDB Docker Image](https://hub.docker.com/_/mariadb) -- [Nginx Docker Image](https://hub.docker.com/_/nginx) -- [Spring Boot Docker Guide](https://spring.io/guides/gs/spring-boot-docker/) - -## ๐Ÿ†˜ Support - -For Docker-related issues: -1. Check the troubleshooting section -2. Review container logs: `docker-compose logs` -3. Verify Docker and Docker Compose versions -4. Check system resources and port availability -5. Ensure all prerequisites are met \ No newline at end of file diff --git a/setup/docker/docker-compose.dev.yml b/setup/docker/docker-compose.dev.yml deleted file mode 100644 index edf9183..0000000 --- a/setup/docker/docker-compose.dev.yml +++ /dev/null @@ -1,83 +0,0 @@ -version: '3.8' - -services: - # MariaDB Database - database: - image: mariadb:10.11 - container_name: easycrud-db-dev - restart: unless-stopped - environment: - MYSQL_ROOT_PASSWORD: rootpassword - MYSQL_DATABASE: student_db - MYSQL_USER: easycrud_user - MYSQL_PASSWORD: easycrud_password - ports: - - "3306:3306" - volumes: - - db_data_dev:/var/lib/mysql - - ./backend/database_schema.sql:/docker-entrypoint-initdb.d/01-schema.sql - networks: - - easycrud-network-dev - - # Spring Boot Backend - backend: - build: - context: ./backend - dockerfile: Dockerfile - container_name: easycrud-backend-dev - restart: unless-stopped - environment: - SPRING_DATASOURCE_URL: jdbc:mariadb://database:3306/student_db - SPRING_DATASOURCE_USERNAME: easycrud_user - SPRING_DATASOURCE_PASSWORD: easycrud_password - SPRING_JPA_HIBERNATE_DDL_AUTO: validate - SPRING_JPA_SHOW_SQL: true - SERVER_PORT: 8080 - ports: - - "8080:8080" - depends_on: - - database - networks: - - easycrud-network-dev - - # React Frontend - frontend: - build: - context: ./frontend - dockerfile: Dockerfile - args: - BACKEND_HOST: 34.201.59.80 - BACKEND_PORT: 8080 - container_name: easycrud-frontend-dev - restart: unless-stopped - ports: - - "3000:3000" - depends_on: - - backend - networks: - - easycrud-network-dev - - # Nginx Reverse Proxy (Optional) - nginx: - image: nginx:alpine - container_name: easycrud-nginx-dev - restart: unless-stopped - ports: - - "80:80" - - "443:443" - volumes: - - ./nginx/nginx.conf:/etc/nginx/nginx.conf - - ./nginx/ssl:/etc/nginx/ssl - depends_on: - - frontend - - backend - networks: - - easycrud-network-dev - -volumes: - db_data_dev: - driver: local - -networks: - easycrud-network-dev: - driver: bridge \ No newline at end of file diff --git a/setup/docker/docker-compose.yml b/setup/docker/docker-compose.yml deleted file mode 100644 index edf9183..0000000 --- a/setup/docker/docker-compose.yml +++ /dev/null @@ -1,83 +0,0 @@ -version: '3.8' - -services: - # MariaDB Database - database: - image: mariadb:10.11 - container_name: easycrud-db-dev - restart: unless-stopped - environment: - MYSQL_ROOT_PASSWORD: rootpassword - MYSQL_DATABASE: student_db - MYSQL_USER: easycrud_user - MYSQL_PASSWORD: easycrud_password - ports: - - "3306:3306" - volumes: - - db_data_dev:/var/lib/mysql - - ./backend/database_schema.sql:/docker-entrypoint-initdb.d/01-schema.sql - networks: - - easycrud-network-dev - - # Spring Boot Backend - backend: - build: - context: ./backend - dockerfile: Dockerfile - container_name: easycrud-backend-dev - restart: unless-stopped - environment: - SPRING_DATASOURCE_URL: jdbc:mariadb://database:3306/student_db - SPRING_DATASOURCE_USERNAME: easycrud_user - SPRING_DATASOURCE_PASSWORD: easycrud_password - SPRING_JPA_HIBERNATE_DDL_AUTO: validate - SPRING_JPA_SHOW_SQL: true - SERVER_PORT: 8080 - ports: - - "8080:8080" - depends_on: - - database - networks: - - easycrud-network-dev - - # React Frontend - frontend: - build: - context: ./frontend - dockerfile: Dockerfile - args: - BACKEND_HOST: 34.201.59.80 - BACKEND_PORT: 8080 - container_name: easycrud-frontend-dev - restart: unless-stopped - ports: - - "3000:3000" - depends_on: - - backend - networks: - - easycrud-network-dev - - # Nginx Reverse Proxy (Optional) - nginx: - image: nginx:alpine - container_name: easycrud-nginx-dev - restart: unless-stopped - ports: - - "80:80" - - "443:443" - volumes: - - ./nginx/nginx.conf:/etc/nginx/nginx.conf - - ./nginx/ssl:/etc/nginx/ssl - depends_on: - - frontend - - backend - networks: - - easycrud-network-dev - -volumes: - db_data_dev: - driver: local - -networks: - easycrud-network-dev: - driver: bridge \ No newline at end of file diff --git a/setup/docker/env.example b/setup/docker/env.example deleted file mode 100644 index 1500a61..0000000 --- a/setup/docker/env.example +++ /dev/null @@ -1,82 +0,0 @@ -# ============================================================================= -# EasyCRUD Docker Environment Configuration -# ============================================================================= -# Copy this file to .env and update the values for your environment - -# ============================================================================= -# Database Configuration -# ============================================================================= -MYSQL_ROOT_PASSWORD=your_secure_root_password_here -MYSQL_DATABASE=student_db -MYSQL_USER=easycrud_user -MYSQL_PASSWORD=your_secure_password_here - -# ============================================================================= -# Backend Configuration -# ============================================================================= -SPRING_PROFILES_ACTIVE=prod -SPRING_DATASOURCE_URL=jdbc:mariadb://database:3306/student_db -SPRING_DATASOURCE_USERNAME=${MYSQL_USER} -SPRING_DATASOURCE_PASSWORD=${MYSQL_PASSWORD} -SPRING_JPA_HIBERNATE_DDL_AUTO=validate -SPRING_JPA_SHOW_SQL=false -SERVER_PORT=8080 - -# JVM Options for Production -JAVA_OPTS=-Xms512m -Xmx1g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UseStringDeduplication -Djava.security.egd=file:/dev/./urandom - -# ============================================================================= -# Frontend Configuration -# ============================================================================= -VITE_API_BASE_URL=http://localhost:8080 -VITE_APP_TITLE=EasyCRUD Student Registration -NODE_ENV=production - -# ============================================================================= -# Nginx Configuration -# ============================================================================= -NGINX_SERVER_NAME=localhost -NGINX_SSL_CERT_PATH=/etc/nginx/ssl/cert.pem -NGINX_SSL_KEY_PATH=/etc/nginx/ssl/key.pem - -# ============================================================================= -# Monitoring Configuration -# ============================================================================= -GRAFANA_PASSWORD=your_grafana_password_here -PROMETHEUS_RETENTION_TIME=200h - -# ============================================================================= -# Redis Configuration (Optional) -# ============================================================================= -REDIS_PASSWORD=your_redis_password_here -REDIS_MAX_MEMORY=256mb - -# ============================================================================= -# Security Configuration -# ============================================================================= -# Generate a secure random string for JWT signing -JWT_SECRET=your_jwt_secret_key_here -# Generate a secure random string for encryption -ENCRYPTION_KEY=your_encryption_key_here - -# ============================================================================= -# Backup Configuration -# ============================================================================= -BACKUP_RETENTION_DAYS=30 -BACKUP_SCHEDULE=0 2 * * * # Daily at 2 AM - -# ============================================================================= -# Development Overrides (uncomment for development) -# ============================================================================= -# SPRING_PROFILES_ACTIVE=dev -# SPRING_JPA_SHOW_SQL=true -# VITE_API_BASE_URL=http://localhost:8080 -# NODE_ENV=development - -# ============================================================================= -# Production Overrides (uncomment for production) -# ============================================================================= -# VITE_API_BASE_URL=https://your-domain.com -# NGINX_SERVER_NAME=your-domain.com -# SPRING_PROFILES_ACTIVE=prod -# SPRING_JPA_SHOW_SQL=false \ No newline at end of file diff --git a/setup/docker/nginx/nginx.conf b/setup/docker/nginx/nginx.conf deleted file mode 100644 index 3b52d98..0000000 --- a/setup/docker/nginx/nginx.conf +++ /dev/null @@ -1,133 +0,0 @@ -events { - worker_connections 1024; -} - -http { - include /etc/nginx/mime.types; - default_type application/octet-stream; - - # Logging - log_format main '$remote_addr - $remote_user [$time_local] "$request" ' - '$status $body_bytes_sent "$http_referer" ' - '"$http_user_agent" "$http_x_forwarded_for"'; - - access_log /var/log/nginx/access.log main; - error_log /var/log/nginx/error.log warn; - - # Basic settings - sendfile on; - tcp_nopush on; - tcp_nodelay on; - keepalive_timeout 65; - types_hash_max_size 2048; - - # Gzip compression - gzip on; - gzip_vary on; - gzip_min_length 1024; - gzip_proxied any; - gzip_comp_level 6; - gzip_types - text/plain - text/css - text/xml - text/javascript - application/json - application/javascript - application/xml+rss - application/atom+xml - image/svg+xml; - - # Rate limiting - limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; - limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s; - - # Upstream servers - upstream frontend { - server frontend:3000; - } - - upstream backend { - server backend:8080; - } - - # HTTP server (redirect to HTTPS in production) - server { - listen 80; - server_name localhost; - - # Security headers - add_header X-Frame-Options "SAMEORIGIN" always; - add_header X-XSS-Protection "1; mode=block" always; - add_header X-Content-Type-Options "nosniff" always; - add_header Referrer-Policy "no-referrer-when-downgrade" always; - - # Frontend routes - location / { - proxy_pass http://frontend; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 30s; - } - - # API routes with rate limiting - location /api/ { - limit_req zone=api burst=20 nodelay; - proxy_pass http://backend; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_connect_timeout 30s; - proxy_send_timeout 30s; - proxy_read_timeout 30s; - } - - # Health check endpoints - location /health { - access_log off; - return 200 "healthy\n"; - add_header Content-Type text/plain; - } - - location /api/health { - proxy_pass http://backend/actuator/health; - proxy_set_header Host $host; - } - - # Static files caching - location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { - expires 1y; - add_header Cache-Control "public, immutable"; - proxy_pass http://frontend; - } - } - - # HTTPS server (for production) - # Uncomment and configure SSL certificates for production use - # server { - # listen 443 ssl http2; - # server_name your-domain.com; - # - # ssl_certificate /etc/nginx/ssl/cert.pem; - # ssl_certificate_key /etc/nginx/ssl/key.pem; - # ssl_protocols TLSv1.2 TLSv1.3; - # ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; - # ssl_prefer_server_ciphers off; - # - # # Same location blocks as HTTP server - # location / { - # proxy_pass http://frontend; - # # ... same proxy settings - # } - # - # location /api/ { - # proxy_pass http://backend; - # # ... same proxy settings - # } - # } -} \ No newline at end of file From 0c10c9001b812c76b795da6e0a5788e34d653aad Mon Sep 17 00:00:00 2001 From: mukundDeo9325 Date: Thu, 12 Feb 2026 16:44:30 +0530 Subject: [PATCH 08/16] new data --- backend/.gitignore | 337 +++++++- backend/DATABASE_SETUP.md | 207 +++++ backend/Dockerfile | 54 +- backend/backend-pod.yaml | 12 - backend/backend-svc.yaml | 11 - backend/database_schema.sql | 148 ++++ backend/mvnw | 0 backend/pom.xml | 117 ++- backend/simple_schema.sql | 40 + .../config/WebConfig.java | 36 +- .../controller/HealthController.java | 19 + .../controller/UserController.java | 23 +- .../model/User.java | 7 - .../repository/UserRepository.java | 9 - .../src/main/resources/application.properties | 9 +- compose.yml | 20 - frontend/.env | 4 +- frontend/.gitignore | 140 +++- frontend/Dockerfile | 86 +- frontend/frontend-deploy.yml | 22 - frontend/frontend-svc.yml | 11 - frontend/index.html | 26 +- frontend/nginx-simple.conf | 55 ++ frontend/nginx.conf | 58 ++ frontend/package-lock.json | 93 ++- frontend/package.json | 68 +- frontend/src/App.jsx | 93 ++- frontend/src/api/userService.js | 64 +- .../src/components/common/ErrorBoundary.css | 191 +++++ .../src/components/common/ErrorBoundary.jsx | 82 ++ frontend/src/components/layout/Layout.css | 225 ++++++ frontend/src/components/layout/Layout.jsx | 66 ++ frontend/src/context/AppContext.jsx | 148 ++++ frontend/src/index.css | 735 ++++++++++++++++++ frontend/src/main.jsx | 22 +- frontend/src/pages/Dashboard.css | 387 +++++++++ frontend/src/pages/Dashboard.jsx | 187 +++++ frontend/src/pages/NotFound.css | 218 ++++++ frontend/src/pages/NotFound.jsx | 49 ++ frontend/src/pages/StudentList.css | 576 ++++++++++++++ frontend/src/pages/StudentList.jsx | 360 +++++++++ frontend/src/pages/StudentRegistration.css | 299 +++++++ frontend/src/pages/StudentRegistration.jsx | 343 ++++++++ frontend/src/utils/config.js | 20 + frontend/vite.config.js | 14 +- setup/docker/DOCKER_SETUP.md | 353 +++++++++ setup/docker/docker-compose.dev.yml | 83 ++ setup/docker/docker-compose.yml | 83 ++ setup/docker/env.example | 82 ++ setup/docker/nginx/nginx.conf | 133 ++++ 50 files changed, 6085 insertions(+), 340 deletions(-) create mode 100644 backend/DATABASE_SETUP.md delete mode 100644 backend/backend-pod.yaml delete mode 100644 backend/backend-svc.yaml create mode 100644 backend/database_schema.sql mode change 100644 => 100755 backend/mvnw create mode 100644 backend/simple_schema.sql create mode 100644 backend/src/main/java/com/student/registration/student_registration_backend/controller/HealthController.java delete mode 100644 backend/src/main/java/com/student/registration/student_registration_backend/repository/UserRepository.java delete mode 100644 compose.yml delete mode 100644 frontend/frontend-deploy.yml delete mode 100644 frontend/frontend-svc.yml create mode 100644 frontend/nginx-simple.conf create mode 100644 frontend/nginx.conf create mode 100644 frontend/src/components/common/ErrorBoundary.css create mode 100644 frontend/src/components/common/ErrorBoundary.jsx create mode 100644 frontend/src/components/layout/Layout.css create mode 100644 frontend/src/components/layout/Layout.jsx create mode 100644 frontend/src/context/AppContext.jsx create mode 100644 frontend/src/index.css create mode 100644 frontend/src/pages/Dashboard.css create mode 100644 frontend/src/pages/Dashboard.jsx create mode 100644 frontend/src/pages/NotFound.css create mode 100644 frontend/src/pages/NotFound.jsx create mode 100644 frontend/src/pages/StudentList.css create mode 100644 frontend/src/pages/StudentList.jsx create mode 100644 frontend/src/pages/StudentRegistration.css create mode 100644 frontend/src/pages/StudentRegistration.jsx create mode 100644 frontend/src/utils/config.js create mode 100644 setup/docker/DOCKER_SETUP.md create mode 100644 setup/docker/docker-compose.dev.yml create mode 100644 setup/docker/docker-compose.yml create mode 100644 setup/docker/env.example create mode 100644 setup/docker/nginx/nginx.conf diff --git a/backend/.gitignore b/backend/.gitignore index 7bc4bf7..75ee0c5 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,10 +1,58 @@ -HELP.md +# Compiled class files +*.class + +# Log files +*.log +logs/ + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# Virtual machine crash logs +hs_err_pid* +replay_pid* + +# Maven target/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties +.mvn/wrapper/maven-wrapper.jar + +# Gradle +.gradle/ +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ -### STS ### +# IntelliJ IDEA +.idea/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +# Eclipse .apt_generated .classpath .factorypath @@ -12,22 +60,279 @@ target/ .settings .springBeans .sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr - -### NetBeans ### +# NetBeans /nbproject/private/ /nbbuild/ /dist/ /nbdist/ /.nb-gradle/ -build/ -!**/src/main/**/build/ -!**/src/test/**/build/ -### VS Code ### +# VS Code .vscode/ + +# Mac +.DS_Store + +# Windows +Thumbs.db +ehthumbs.db +Desktop.ini + +# Linux +*~ + +# Application specific +application-local.properties +application-dev.properties +application-prod.properties +application-test.properties + +# Environment variables +.env +.env.local +.env.development +.env.test +.env.production + +# Database +*.db +*.sqlite +*.sqlite3 + +# Temporary files +*.tmp +*.temp +temp/ +tmp/ + +# Backup files +*.bak +*.backup +*.old + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Spring Boot specific +spring.log +application.log + +# H2 Database +*.h2.db +*.h2.db.trace + +# Liquibase +dbchangelog.sql + +# Flyway +flyway.sql + +# Test reports +test-output/ +reports/ +coverage/ + +# Performance monitoring +*.hprof + +# Docker +.dockerignore +docker-compose.override.yml + +# Kubernetes +*.yaml.bak +*.yml.bak + +# Local configuration +config/local/ +config/dev/ +config/test/ + +# SSL certificates +*.p12 +*.pem +*.key +*.crt +*.csr + +# JWT tokens +jwt-secret.txt +jwt-public.key +jwt-private.key + +# API documentation +swagger-ui/ +api-docs/ + +# Monitoring +prometheus.yml +grafana/ + +# Log aggregation +logstash/ +elasticsearch/ + +# Cache +.cache/ +cache/ + +# Profiling +*.prof +*.jfr + +# Memory dumps +*.hprof +*.dump + +# IDE specific +.project +.classpath +.settings/ +.metadata/ +*.launch + +# Build tools +ant/ +ant-build/ +build.xml + +# Package managers +package-lock.json +yarn.lock +pnpm-lock.yaml + +# Node.js (if using Node.js tools) +node_modules/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Python (if using Python tools) +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Ruby (if using Ruby tools) +*.gem +*.rbc +/.config +/coverage/ +/InstalledFiles +/pkg/ +/spec/reports/ +/spec/examples.txt +/test/tmp/ +/test/version_tmp/ +/tmp/ + +# Go (if using Go tools) +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.test +*.out +go.work + +# Rust (if using Rust tools) +/target/ +Cargo.lock + +# PHP (if using PHP tools) +/vendor/ +composer.phar +composer.lock + +# .NET (if using .NET tools) +*.user +*.userosscache +*.suo +*.userprefs +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Scala (if using Scala tools) +*.class +*.log +.cache +.history +.repl_history +project/ +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ + +# Clojure (if using Clojure tools) +.cpcache/ +.lein-* +.nrepl-port +.strepl-port +*.jar +*.class + +# Leiningen +target/ +classes/ +checkouts/ +pom.xml +pom.xml.asc +*.jar +*.class + +# Boot +.boot/ + +# Profiles +profiles.clj + +# Local configuration +local.clj + +# Clojure REPL +.nrepl-port +.strepl-port + +# ClojureScript +.cache/ +.parcel-cache/ +dist/ +build/ + +# ClojureScript REPL +.rebel_readline_history + +# ClojureScript DevTools +.cljsbuild/ + +# ClojureScript Karma +karma.conf.js + +# ClojureScript Karma +karma.conf.js + +# ClojureScript Karma +karma.conf.js diff --git a/backend/DATABASE_SETUP.md b/backend/DATABASE_SETUP.md new file mode 100644 index 0000000..6fd9016 --- /dev/null +++ b/backend/DATABASE_SETUP.md @@ -0,0 +1,207 @@ +# Database Setup Guide + +This guide explains how to set up the database for the Student Registration System. + +## Database Schema Overview + +The schema includes the following tables: + +### Core Tables +- **users** - Main table for student information +- **courses** - Available courses/programs +- **branches** - Different engineering branches +- **user_roles** - Role definitions (STUDENT, ADMIN, FACULTY, STAFF) +- **user_role_mapping** - Many-to-many relationship between users and roles +- **audit_logs** - Track changes to data for audit purposes + +## Setup Instructions + +### 1. Choose Your Database System + +The schema is compatible with: +- **MySQL** (5.7+) +- **MariaDB** (10.2+) +- **PostgreSQL** (with minor modifications) + +### 2. Create Database + +#### For MySQL/MariaDB: +```sql +CREATE DATABASE student_db; +USE student_db; +``` + +#### For PostgreSQL: +```sql +CREATE DATABASE student_db; +\c student_db; +``` + +### 3. Import Schema + +#### Option A: Using Command Line +```bash +# MySQL +mysql -u your_username -p student_db < database_schema.sql + +# MariaDB +mariadb -u your_username -p student_db < database_schema.sql + +# PostgreSQL +psql -U your_username -d student_db -f database_schema.sql +``` + +#### Option B: Using Database GUI Tools +- **MySQL Workbench**: File โ†’ Open SQL Script โ†’ Select `database_schema.sql` +- **phpMyAdmin**: Import โ†’ Choose File โ†’ Select `database_schema.sql` +- **pgAdmin**: Tools โ†’ Query Tool โ†’ Load file โ†’ Select `database_schema.sql` + +### 4. Verify Setup + +After importing, you should see: +- 6 tables created +- 10 sample users inserted +- 6 courses inserted +- 5 branches inserted +- 4 user roles inserted +- Sample data relationships established + +## Database Configuration + +### For Spring Boot Application + +When you're ready to add database support back to your Spring Boot application, update your `application.properties`: + +```properties +# Database Configuration +spring.datasource.url=jdbc:mysql://localhost:3306/student_db +spring.datasource.username=your_username +spring.datasource.password=your_password +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +# JPA Configuration +spring.jpa.hibernate.ddl-auto=validate +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect +spring.jpa.properties.hibernate.format_sql=true + +# Connection Pool +spring.datasource.hikari.maximum-pool-size=10 +spring.datasource.hikari.minimum-idle=5 +spring.datasource.hikari.idle-timeout=300000 +``` + +### For PostgreSQL: +```properties +spring.datasource.url=jdbc:postgresql://localhost:5432/student_db +spring.datasource.username=your_username +spring.datasource.password=your_password +spring.datasource.driver-class-name=org.postgresql.Driver +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.PostgreSQLDialect +``` + +## Table Structure Details + +### Users Table +```sql +CREATE TABLE users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + course VARCHAR(255), + student_class VARCHAR(100), + percentage DECIMAL(5,2), + branch VARCHAR(255), + mobile_number VARCHAR(20), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); +``` + +### Key Features: +- **Auto-incrementing ID**: Primary key +- **Email uniqueness**: Prevents duplicate registrations +- **Audit fields**: `created_at` and `updated_at` timestamps +- **Indexes**: Optimized for email, course, and branch queries + +## Sample Data + +The schema includes 10 sample students with realistic data: +- Various engineering courses (CS, EE, ME, CE, IT, ECE) +- Different academic years (First Year to Final Year) +- Realistic percentage scores (76-92%) +- Different branches and mobile numbers + +## Security Considerations + +### 1. User Authentication +- Implement password hashing (BCrypt recommended) +- Add password field to users table +- Use Spring Security for authentication + +### 2. Role-Based Access Control +- Use the `user_roles` and `user_role_mapping` tables +- Implement @PreAuthorize annotations in controllers +- Create different access levels for students, faculty, and admins + +### 3. Data Validation +- Add constraints for email format +- Validate percentage range (0-100) +- Ensure mobile number format + +## Backup and Maintenance + +### Create Backup +```bash +# MySQL +mysqldump -u username -p student_db > backup_$(date +%Y%m%d_%H%M%S).sql + +# PostgreSQL +pg_dump -U username student_db > backup_$(date +%Y%m%d_%H%M%S).sql +``` + +### Restore Backup +```bash +# MySQL +mysql -u username -p student_db < backup_file.sql + +# PostgreSQL +psql -U username -d student_db < backup_file.sql +``` + +## Troubleshooting + +### Common Issues: + +1. **Connection Refused** + - Check if database service is running + - Verify port numbers (MySQL: 3306, PostgreSQL: 5432) + +2. **Access Denied** + - Verify username and password + - Check user permissions on database + +3. **Table Already Exists** + - The schema includes `DROP TABLE IF EXISTS` statements + - This will safely remove existing tables + +4. **Character Encoding Issues** + - Ensure database uses UTF-8 encoding + - Add `?useUnicode=true&characterEncoding=UTF-8` to JDBC URL + +## Next Steps + +1. **Import the schema** using the instructions above +2. **Test the database** with sample queries +3. **Update your Spring Boot application** to use database instead of in-memory storage +4. **Add authentication and authorization** using Spring Security +5. **Implement data validation** and error handling +6. **Set up regular backups** for production use + +## Support + +If you encounter any issues: +1. Check the database logs for error messages +2. Verify your database version compatibility +3. Ensure all required permissions are granted +4. Test with a simple connection first \ No newline at end of file diff --git a/backend/Dockerfile b/backend/Dockerfile index 70f0a37..898202e 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,10 +1,46 @@ -FROM maven:3.8.3-openjdk-17 -COPY . /opt/ -WORKDIR /opt -RUN rm -f src/main/resources/application.properties && \ - cp -f application.properties src/main/resources/application.properties && \ - mvn clean package -WORKDIR target/ +# Multi-stage build for Spring Boot application +FROM maven:3.9.6-eclipse-temurin-17 AS build + +# Set working directory +WORKDIR /app + +# Copy pom.xml and download dependencies +COPY pom.xml . +RUN mvn dependency:go-offline -B + +# Copy source code +COPY src ./src + +# Build the application +RUN mvn clean package -DskipTests + +# Runtime stage +FROM eclipse-temurin:17-jre + +# Install curl for health checks +RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* + +# Create app user +RUN groupadd -r appuser && useradd -r -g appuser appuser + +# Set working directory +WORKDIR /app + +# Copy the built JAR from build stage +COPY --from=build /app/target/student-registration-backend-0.0.1-SNAPSHOT.jar app.jar + +# Change ownership to app user +RUN chown appuser:appuser app.jar + +# Switch to app user +USER appuser + +# Expose port EXPOSE 8080 -ENTRYPOINT ["java","-jar"] -CMD ["student-registration-backend-0.0.1-SNAPSHOT.jar"] + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ + CMD curl -f http://localhost:8080/health || exit 1 + +# Run the application +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/backend/backend-pod.yaml b/backend/backend-pod.yaml deleted file mode 100644 index 6c6fa6f..0000000 --- a/backend/backend-pod.yaml +++ /dev/null @@ -1,12 +0,0 @@ -apiVersion: v1 -kind: Pod -metadata: - name: backend - labels: - app: backend -spec: - containers: - - name: backend - image: r123mahajan/backend:latest - ports: - - containerPort: 80 diff --git a/backend/backend-svc.yaml b/backend/backend-svc.yaml deleted file mode 100644 index 73f6b6a..0000000 --- a/backend/backend-svc.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: backend-svc -spec: - type: LoadBalancer - selector: - app: backend - ports: - - port: 8080 - targetPort: 8080 diff --git a/backend/database_schema.sql b/backend/database_schema.sql new file mode 100644 index 0000000..39e7a04 --- /dev/null +++ b/backend/database_schema.sql @@ -0,0 +1,148 @@ +-- Database Schema for Student Registration System +-- This file contains the complete database setup for the application + +-- Create database (uncomment and modify as needed for your database system) +-- CREATE DATABASE student_db; +-- USE student_db; + +-- Drop table if exists (for clean setup) +DROP TABLE IF EXISTS users; + +-- Create users table +CREATE TABLE users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + course VARCHAR(255), + student_class VARCHAR(100), + percentage DECIMAL(5,2), + branch VARCHAR(255), + mobile_number VARCHAR(20), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- Create indexes for better performance +CREATE INDEX idx_users_email ON users(email); +CREATE INDEX idx_users_course ON users(course); +CREATE INDEX idx_users_branch ON users(branch); + +-- Insert sample data +INSERT INTO users (name, email, course, student_class, percentage, branch, mobile_number) VALUES +('John Doe', 'john.doe@example.com', 'Computer Science', 'Final Year', 85.50, 'Computer Engineering', '+1234567890'), +('Jane Smith', 'jane.smith@example.com', 'Electrical Engineering', 'Third Year', 78.25, 'Electrical Engineering', '+1234567891'), +('Mike Johnson', 'mike.johnson@example.com', 'Mechanical Engineering', 'Second Year', 82.75, 'Mechanical Engineering', '+1234567892'), +('Sarah Wilson', 'sarah.wilson@example.com', 'Computer Science', 'First Year', 90.00, 'Computer Engineering', '+1234567893'), +('David Brown', 'david.brown@example.com', 'Civil Engineering', 'Final Year', 76.80, 'Civil Engineering', '+1234567894'), +('Emily Davis', 'emily.davis@example.com', 'Information Technology', 'Third Year', 88.90, 'Computer Engineering', '+1234567895'), +('Robert Miller', 'robert.miller@example.com', 'Electronics Engineering', 'Second Year', 79.45, 'Electronics Engineering', '+1234567896'), +('Lisa Garcia', 'lisa.garcia@example.com', 'Computer Science', 'First Year', 92.30, 'Computer Engineering', '+1234567897'), +('James Rodriguez', 'james.rodriguez@example.com', 'Mechanical Engineering', 'Final Year', 81.20, 'Mechanical Engineering', '+1234567898'), +('Maria Martinez', 'maria.martinez@example.com', 'Electrical Engineering', 'Third Year', 87.60, 'Electrical Engineering', '+1234567899'); + +-- Create additional tables for enhanced functionality (optional) + +-- Create courses table +CREATE TABLE courses ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + course_name VARCHAR(255) NOT NULL UNIQUE, + course_code VARCHAR(50) NOT NULL UNIQUE, + duration_years INT DEFAULT 4, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Create branches table +CREATE TABLE branches ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + branch_name VARCHAR(255) NOT NULL UNIQUE, + branch_code VARCHAR(50) NOT NULL UNIQUE, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Insert sample courses +INSERT INTO courses (course_name, course_code, duration_years, description) VALUES +('Computer Science', 'CS', 4, 'Bachelor of Technology in Computer Science'), +('Electrical Engineering', 'EE', 4, 'Bachelor of Technology in Electrical Engineering'), +('Mechanical Engineering', 'ME', 4, 'Bachelor of Technology in Mechanical Engineering'), +('Civil Engineering', 'CE', 4, 'Bachelor of Technology in Civil Engineering'), +('Information Technology', 'IT', 4, 'Bachelor of Technology in Information Technology'), +('Electronics Engineering', 'ECE', 4, 'Bachelor of Technology in Electronics Engineering'); + +-- Insert sample branches +INSERT INTO branches (branch_name, branch_code, description) VALUES +('Computer Engineering', 'CSE', 'Computer Science and Engineering'), +('Electrical Engineering', 'EEE', 'Electrical and Electronics Engineering'), +('Mechanical Engineering', 'MECH', 'Mechanical Engineering'), +('Civil Engineering', 'CIVIL', 'Civil Engineering'), +('Electronics Engineering', 'ECE', 'Electronics and Communication Engineering'); + +-- Create user_roles table for role-based access (optional) +CREATE TABLE user_roles ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + role_name VARCHAR(50) NOT NULL UNIQUE, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Insert default roles +INSERT INTO user_roles (role_name, description) VALUES +('STUDENT', 'Regular student user'), +('ADMIN', 'Administrator with full access'), +('FACULTY', 'Faculty member'), +('STAFF', 'Staff member'); + +-- Create user_role_mapping table (optional) +CREATE TABLE user_role_mapping ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + user_id BIGINT NOT NULL, + role_id BIGINT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, + FOREIGN KEY (role_id) REFERENCES user_roles(id) ON DELETE CASCADE, + UNIQUE KEY unique_user_role (user_id, role_id) +); + +-- Assign default role to existing users +INSERT INTO user_role_mapping (user_id, role_id) +SELECT u.id, r.id +FROM users u, user_roles r +WHERE r.role_name = 'STUDENT'; + +-- Create audit log table for tracking changes (optional) +CREATE TABLE audit_logs ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + table_name VARCHAR(100) NOT NULL, + record_id BIGINT NOT NULL, + action VARCHAR(20) NOT NULL, -- INSERT, UPDATE, DELETE + old_values JSON, + new_values JSON, + user_id BIGINT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP +); + +-- Create indexes for audit logs +CREATE INDEX idx_audit_logs_table_record ON audit_logs(table_name, record_id); +CREATE INDEX idx_audit_logs_created_at ON audit_logs(created_at); + +-- Show table structure +DESCRIBE users; +DESCRIBE courses; +DESCRIBE branches; +DESCRIBE user_roles; +DESCRIBE user_role_mapping; +DESCRIBE audit_logs; + +-- Show sample data +SELECT 'Users Table:' as table_name; +SELECT * FROM users LIMIT 5; + +SELECT 'Courses Table:' as table_name; +SELECT * FROM courses; + +SELECT 'Branches Table:' as table_name; +SELECT * FROM branches; + +SELECT 'User Roles Table:' as table_name; +SELECT * FROM user_roles; \ No newline at end of file diff --git a/backend/mvnw b/backend/mvnw old mode 100644 new mode 100755 diff --git a/backend/pom.xml b/backend/pom.xml index 48e3faa..9743e2f 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -1,65 +1,64 @@ - 4.0.0 + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.3.5 + + + com.student.registration + student-registration-backend + 0.0.1-SNAPSHOT + student-registration-backend + Demo project for Spring Boot + + + + + + + + + + + + + + + 17 + + + + org.springframework.boot + spring-boot-starter-web + - - org.springframework.boot - spring-boot-starter-parent - 3.3.5 - - + + org.springframework.boot + spring-boot-starter-test + test + + + org.projectlombok + lombok + 1.18.30 + provided + + + org.springframework.boot + spring-boot-starter-actuator + + - com.student.registration - student-registration-backend - 0.0.1-SNAPSHOT - student-registration-backend - Demo project for Spring Boot - - - 17 - - - - - - org.springframework.boot - spring-boot-starter-data-jpa - - - - org.mariadb.jdbc - mariadb-java-client - runtime - - - - org.springframework.boot - spring-boot-starter-web - - - - org.springframework.boot - spring-boot-starter-test - test - - - - org.projectlombok - lombok - 1.18.30 - provided - - - - - - - - org.springframework.boot - spring-boot-maven-plugin - - - + + + + org.springframework.boot + spring-boot-maven-plugin + + + diff --git a/backend/simple_schema.sql b/backend/simple_schema.sql new file mode 100644 index 0000000..0e4a593 --- /dev/null +++ b/backend/simple_schema.sql @@ -0,0 +1,40 @@ +-- Simple Database Schema for Student Registration System +-- This is a minimal version with just the essential tables + +-- Create database (uncomment and modify as needed) +-- CREATE DATABASE student_db; +-- USE student_db; + +-- Drop existing tables if they exist +DROP TABLE IF EXISTS users; + +-- Create the main users table +CREATE TABLE users ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL UNIQUE, + course VARCHAR(255), + student_class VARCHAR(100), + percentage DECIMAL(5,2), + branch VARCHAR(255), + mobile_number VARCHAR(20), + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +); + +-- Create basic indexes +CREATE INDEX idx_users_email ON users(email); + +-- Insert sample data +INSERT INTO users (name, email, course, student_class, percentage, branch, mobile_number) VALUES +('John Doe', 'john.doe@example.com', 'Computer Science', 'Final Year', 85.50, 'Computer Engineering', '+1234567890'), +('Jane Smith', 'jane.smith@example.com', 'Electrical Engineering', 'Third Year', 78.25, 'Electrical Engineering', '+1234567891'), +('Mike Johnson', 'mike.johnson@example.com', 'Mechanical Engineering', 'Second Year', 82.75, 'Mechanical Engineering', '+1234567892'), +('Sarah Wilson', 'sarah.wilson@example.com', 'Computer Science', 'First Year', 90.00, 'Computer Engineering', '+1234567893'), +('David Brown', 'david.brown@example.com', 'Civil Engineering', 'Final Year', 76.80, 'Civil Engineering', '+1234567894'); + +-- Show table structure +DESCRIBE users; + +-- Show sample data +SELECT * FROM users; \ No newline at end of file diff --git a/backend/src/main/java/com/student/registration/student_registration_backend/config/WebConfig.java b/backend/src/main/java/com/student/registration/student_registration_backend/config/WebConfig.java index 12dbbde..8d2258c 100644 --- a/backend/src/main/java/com/student/registration/student_registration_backend/config/WebConfig.java +++ b/backend/src/main/java/com/student/registration/student_registration_backend/config/WebConfig.java @@ -1,18 +1,18 @@ -package com.student.registration.student_registration_backend.config; - -import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; - -@Configuration -public class WebConfig implements WebMvcConfigurer { - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping("/api/**") - .allowedOriginPatterns("*") - .allowedMethods("GET", "POST", "PUT", "DELETE") - .allowedHeaders("*") - .allowCredentials(true); - } -} +package com.student.registration.student_registration_backend.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/**") + .allowedOriginPatterns("*") + .allowedMethods("GET", "POST", "PUT", "DELETE") + .allowedHeaders("*") + .allowCredentials(true); + } +} diff --git a/backend/src/main/java/com/student/registration/student_registration_backend/controller/HealthController.java b/backend/src/main/java/com/student/registration/student_registration_backend/controller/HealthController.java new file mode 100644 index 0000000..05cf008 --- /dev/null +++ b/backend/src/main/java/com/student/registration/student_registration_backend/controller/HealthController.java @@ -0,0 +1,19 @@ +package com.student.registration.student_registration_backend.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class HealthController { + + @GetMapping("/health") + public ResponseEntity health() { + return ResponseEntity.ok("OK"); + } + + @GetMapping("/") + public ResponseEntity root() { + return ResponseEntity.ok("EasyCRUD Backend is running!"); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/student/registration/student_registration_backend/controller/UserController.java b/backend/src/main/java/com/student/registration/student_registration_backend/controller/UserController.java index fad9bc7..3e33569 100644 --- a/backend/src/main/java/com/student/registration/student_registration_backend/controller/UserController.java +++ b/backend/src/main/java/com/student/registration/student_registration_backend/controller/UserController.java @@ -1,38 +1,43 @@ package com.student.registration.student_registration_backend.controller; import com.student.registration.student_registration_backend.model.User; -import com.student.registration.student_registration_backend.repository.UserRepository; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; +import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; @RestController @RequestMapping("/api") public class UserController { - @Autowired - private UserRepository userRepository; + private final List users = new ArrayList<>(); + private final AtomicLong idCounter = new AtomicLong(1); @PostMapping("/register") public User registerUser(@RequestBody User user) { - return userRepository.save(user); + user.setId(idCounter.getAndIncrement()); + users.add(user); + return user; } @GetMapping("/users") public List getAllUsers() { - return userRepository.findAll(); + return new ArrayList<>(users); } // Delete user by ID @DeleteMapping("/users/{id}") public ResponseEntity deleteUser(@PathVariable Long id) { - Optional user = userRepository.findById(id); - if (user.isPresent()) { - userRepository.deleteById(id); + Optional userToDelete = users.stream() + .filter(user -> user.getId().equals(id)) + .findFirst(); + + if (userToDelete.isPresent()) { + users.remove(userToDelete.get()); return ResponseEntity.ok("User deleted successfully"); } else { return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found"); diff --git a/backend/src/main/java/com/student/registration/student_registration_backend/model/User.java b/backend/src/main/java/com/student/registration/student_registration_backend/model/User.java index 0decdeb..dd8f854 100644 --- a/backend/src/main/java/com/student/registration/student_registration_backend/model/User.java +++ b/backend/src/main/java/com/student/registration/student_registration_backend/model/User.java @@ -1,17 +1,10 @@ package com.student.registration.student_registration_backend.model; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; import lombok.Data; -@Entity @Data // This annotation from Lombok automatically generates getters, setters, and other methods. public class User { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; diff --git a/backend/src/main/java/com/student/registration/student_registration_backend/repository/UserRepository.java b/backend/src/main/java/com/student/registration/student_registration_backend/repository/UserRepository.java deleted file mode 100644 index a4acccc..0000000 --- a/backend/src/main/java/com/student/registration/student_registration_backend/repository/UserRepository.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.student.registration.student_registration_backend.repository; - -import com.student.registration.student_registration_backend.model.User; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; - -@Repository -public interface UserRepository extends JpaRepository { -} diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 9cce4ea..4c00e40 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,8 +1 @@ -server.port=8080 - -spring.datasource.url=jdbc:mariadb://database-1.cyz6e8o02c2r.us-east-1.rds.amazonaws.com:3306/student_db?sslMode=trust -spring.datasource.username=admin -spring.datasource.password=redhat123 - -spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true +server.port=8080 diff --git a/compose.yml b/compose.yml deleted file mode 100644 index 70f6b2d..0000000 --- a/compose.yml +++ /dev/null @@ -1,20 +0,0 @@ -version: "3.8" -services: - backend: - build: - context: ./backend - dockerfile: dockerfile - ports: - - 8080:8080 - # depends_on: - # -db - - frontend: - build: - context: ./frontend - dockerfile: dockerfile - ports: - - 80:80 - depends_on: - - backend - diff --git a/frontend/.env b/frontend/.env index 5d6b475..19cdadd 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1 +1,3 @@ -VITE_API_URL=http://3.237.105.61:8080/api +VITE_API_URL=http://$BACKEND:8080/api +VITE_API_BASE_URL=http://$BACKEND:8080 +VITE_APP_TITLE=EasyCRUD Student Registration diff --git a/frontend/.gitignore b/frontend/.gitignore index 4108b33..1421f2a 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -1,24 +1,148 @@ -# Logs -logs -*.log +# Dependencies +node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* lerna-debug.log* -node_modules -dist -dist-ssr -*.local +# Build outputs +dist/ +build/ +*.tgz +*.tar.gz + +# Environment variables +.env.local +.env.development.local +.env.test.local +.env.production.local + +# IDE and Editor files +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Logs +logs +*.log + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Coverage directory used by tools like istanbul +coverage/ +*.lcov + +# nyc test coverage +.nyc_output + +# Dependency directories +jspm_packages/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next + +# Nuxt.js build / generate output +.nuxt + +# Gatsby files +.cache/ +public + +# Storybook build outputs +.out +.storybook-out + +# Temporary folders +tmp/ +temp/ # Editor directories and files .vscode/* !.vscode/extensions.json .idea -.DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? + +# Local Netlify folder +.netlify + +# Vite +.vite + +# TypeScript +*.tsbuildinfo + +# Testing +/coverage +/.nyc_output + +# Production +/build + +# Misc +.DS_Store +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Local env files +.env*.local + +# Vercel +.vercel + +# Turbo +.turbo diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 9294a0e..51e950b 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,9 +1,77 @@ -FROM node:24-alpine -COPY . /opt/ -WORKDIR /opt -RUN npm install && npm run build -RUN apk update && apk add apache2 -RUN rm -rf /var/www/localhost/htdocs/* -RUN cp -rf dist/* /var/www/localhost/htdocs -EXPOSE 80 -ENTRYPOINT ["httpd","-D","FOREGROUND"] +# Multi-stage build for React application +FROM node:18-alpine AS build + +# Set working directory +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install dependencies (including dev dependencies for build) +RUN npm ci + +# Copy source code +COPY . . + +# Build arguments for backend IP detection +ARG BACKEND_HOST +ARG BACKEND_PORT=8080 + +# Install curl for IP detection +RUN apk add --no-cache curl + +# Detect backend IP and update .env file +RUN if [ -n "$BACKEND_HOST" ]; then \ + # If BACKEND_HOST is provided, use it directly + BACKEND_IP="$BACKEND_HOST"; \ + else \ + # Try to detect the backend IP automatically + BACKEND_IP=$(curl -s ifconfig.me 2>/dev/null || echo "localhost"); \ + fi && \ + echo "Detected backend IP: $BACKEND_IP" && \ + if [ -f .env ]; then \ + sed -i "s/\$BACKEND/$BACKEND_IP/g" .env; \ + echo "Updated .env file with backend IP: $BACKEND_IP"; \ + cat .env; \ + else \ + echo "VITE_API_URL=http://$BACKEND_IP:$BACKEND_PORT/api" > .env; \ + echo "VITE_API_BASE_URL=http://$BACKEND_IP:$BACKEND_PORT" >> .env; \ + echo "VITE_APP_TITLE=EasyCRUD Student Registration" >> .env; \ + echo "Created .env file with backend IP: $BACKEND_IP"; \ + fi + +# Build the application +RUN npm run build + +# Production stage with nginx +FROM nginx:alpine + +# Install curl for health checks +RUN apk add --no-cache curl + +# Copy built application from build stage +COPY --from=build /app/dist /usr/share/nginx/html + +# Copy custom nginx configuration +COPY nginx-simple.conf /etc/nginx/conf.d/default.conf + +# Use existing nginx user (already exists in nginx:alpine) + +# Change ownership of nginx directories +RUN chown -R nginx:nginx /var/cache/nginx && \ + chown -R nginx:nginx /var/log/nginx && \ + chown -R nginx:nginx /etc/nginx/conf.d && \ + chown -R nginx:nginx /usr/share/nginx/html + +# Run as root for proper nginx startup +USER root + +# Expose port +EXPOSE 3000 + +# Health check +HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \ + CMD curl -f http://localhost:3000 || exit 1 + +# Start nginx +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/frontend/frontend-deploy.yml b/frontend/frontend-deploy.yml deleted file mode 100644 index 5dc1d23..0000000 --- a/frontend/frontend-deploy.yml +++ /dev/null @@ -1,22 +0,0 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: frontend - labels: - app: frontend -spec: - replicas: 3 - selector: - matchLabels: - app: frontend - template: - metadata: - name: frontend - labels: - app: frontend - spec: - containers: - - name: frontend - image: r123mahajan/frontend:latest - ports: - - containerPort: 80 diff --git a/frontend/frontend-svc.yml b/frontend/frontend-svc.yml deleted file mode 100644 index 2a5419e..0000000 --- a/frontend/frontend-svc.yml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: frontend-svc -spec: - type: LoadBalancer - selector: - app: frontend - ports: - - port: 80 - targetPort: 80 diff --git a/frontend/index.html b/frontend/index.html index 77a4b65..90d86bf 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,13 +1,13 @@ - - - - - - - CLOUDBLITZ | Student Registration - - -
- - - + + + + + + + CLOUDBLITZ | Student Registration + + +
+ + + diff --git a/frontend/nginx-simple.conf b/frontend/nginx-simple.conf new file mode 100644 index 0000000..2d2caa7 --- /dev/null +++ b/frontend/nginx-simple.conf @@ -0,0 +1,55 @@ +server { + listen 3000; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Enable gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_types text/plain text/css text/xml text/javascript application/javascript; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + + # Handle React Router (SPA) + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + # API proxy (if needed) + location /api/ { + proxy_pass http://backend:8080/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 30s; + proxy_send_timeout 30s; + proxy_read_timeout 30s; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Error pages + error_page 404 /index.html; + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} \ No newline at end of file diff --git a/frontend/nginx.conf b/frontend/nginx.conf new file mode 100644 index 0000000..ce95886 --- /dev/null +++ b/frontend/nginx.conf @@ -0,0 +1,58 @@ +server { + listen 3000; + server_name localhost; + root /usr/share/nginx/html; + index index.html; + + # Enable gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied expired no-cache no-store private auth; + gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml+rss application/javascript; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + add_header Content-Security-Policy "default-src 'self' http: https: data: blob: 'unsafe-inline'" always; + + # Handle React Router (SPA) + location / { + try_files $uri $uri/ /index.html; + } + + # Cache static assets + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + try_files $uri =404; + } + + # API proxy (if needed) + location /api/ { + proxy_pass http://backend:8080/api/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 30s; + proxy_send_timeout 30s; + proxy_read_timeout 30s; + } + + # Health check endpoint + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + # Error pages + error_page 404 /index.html; + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } +} \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 29aa827..de4746e 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,10 +8,14 @@ "name": "student-registration-frontend", "version": "0.0.0", "dependencies": { + "@tanstack/react-query": "^5.59.16", + "@tanstack/react-query-devtools": "^5.59.16", "@testing-library/react": "^16.1.0", "axios": "^1.7.7", "react": "^18.3.1", "react-dom": "^18.3.1", + "react-hot-toast": "^2.4.1", + "react-icons": "^5.0.1", "react-router-dom": "^6.28.0", "web-vitals": "^4.2.4" }, @@ -1242,6 +1246,59 @@ "win32" ] }, + "node_modules/@tanstack/query-core": { + "version": "5.83.0", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.83.0.tgz", + "integrity": "sha512-0M8dA+amXUkyz5cVUm/B+zSk3xkQAcuXuz5/Q/LveT4ots2rBpPTZOzd7yJa2Utsf8D2Upl5KyjhHRY+9lB/XA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/query-devtools": { + "version": "5.81.2", + "resolved": "https://registry.npmjs.org/@tanstack/query-devtools/-/query-devtools-5.81.2.tgz", + "integrity": "sha512-jCeJcDCwKfoyyBXjXe9+Lo8aTkavygHHsUHAlxQKKaDeyT0qyQNLKl7+UyqYH2dDF6UN/14873IPBHchcsU+Zg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.83.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.83.0.tgz", + "integrity": "sha512-/XGYhZ3foc5H0VM2jLSD/NyBRIOK4q9kfeml4+0x2DlL6xVuAcVEW+hTlTapAmejObg0i3eNqhkr2dT+eciwoQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.83.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@tanstack/react-query-devtools": { + "version": "5.83.0", + "resolved": "https://registry.npmjs.org/@tanstack/react-query-devtools/-/react-query-devtools-5.83.0.tgz", + "integrity": "sha512-yfp8Uqd3I1jgx8gl0lxbSSESu5y4MO2ThOPBnGNTYs0P+ZFu+E9g5IdOngyUGuo6Uz6Qa7p9TLdZEX3ntik2fQ==", + "license": "MIT", + "dependencies": { + "@tanstack/query-devtools": "5.81.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "@tanstack/react-query": "^5.83.0", + "react": "^18 || ^19" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -1836,7 +1893,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "devOptional": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -2700,6 +2756,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/goober": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.16.tgz", + "integrity": "sha512-erjk19y1U33+XAMe1VTvIONHYoSqE4iS7BYUZfHaqeohLmnC0FdxEh7rQU+6MZ4OajItzjZFSRtVANrQwNq6/g==", + "license": "MIT", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -3810,6 +3875,32 @@ "react": "^18.3.1" } }, + "node_modules/react-hot-toast": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.5.2.tgz", + "integrity": "sha512-Tun3BbCxzmXXM7C+NI4qiv6lT0uwGh4oAfeJyNOjYUejTsm35mK9iCaYLGv8cBz9L5YxZLx/2ii7zsIwPtPUdw==", + "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", + "integrity": "sha512-MEFcXdkP3dLo8uumGI5xN3lDFNsRtrjbOEKDLD7yv76v4wpnEq2Lt2qeHaQOr34I/wPN3s3+N08WkQ+CW37Xiw==", + "license": "MIT", + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index 7d7b962..dde43e8 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,32 +1,36 @@ -{ - "name": "student-registration-frontend", - "private": true, - "version": "0.0.0", - "type": "module", - "scripts": { - "dev": "vite", - "build": "vite build", - "lint": "eslint .", - "preview": "vite preview" - }, - "dependencies": { - "@testing-library/react": "^16.1.0", - "axios": "^1.7.7", - "react": "^18.3.1", - "react-dom": "^18.3.1", - "react-router-dom": "^6.28.0", - "web-vitals": "^4.2.4" - }, - "devDependencies": { - "@eslint/js": "^9.13.0", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.3.3", - "eslint": "^9.13.0", - "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-react-refresh": "^0.4.14", - "globals": "^15.11.0", - "vite": "^5.4.10" - } -} +{ + "name": "student-registration-frontend", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/react-query": "^5.59.16", + "@tanstack/react-query-devtools": "^5.59.16", + "@testing-library/react": "^16.1.0", + "axios": "^1.7.7", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-hot-toast": "^2.4.1", + "react-icons": "^5.0.1", + "react-router-dom": "^6.28.0", + "web-vitals": "^4.2.4" + }, + "devDependencies": { + "@eslint/js": "^9.13.0", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.3", + "eslint": "^9.13.0", + "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.11.0", + "vite": "^5.4.10" + } +} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index a86b4a2..4d02693 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,15 +1,78 @@ -import React from "react"; -import { BrowserRouter as Router, Route, Routes } from "react-router-dom"; -import RegistrationForm from "./components/RegistrationForm"; - -const App = () => { - return ( - - - } /> - - - ); -}; - -export default App; +import React from 'react'; +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { Toaster } from 'react-hot-toast'; + +// Layout Components +import Layout from './components/layout/Layout'; +import ErrorBoundary from './components/common/ErrorBoundary'; + +// Pages +import Dashboard from './pages/Dashboard'; +import StudentRegistration from './pages/StudentRegistration'; +import StudentList from './pages/StudentList'; +import NotFound from './pages/NotFound'; + +// Context Providers +import { AppProvider } from './context/AppContext'; + +// Create a client for React Query +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 5 * 60 * 1000, // 5 minutes + cacheTime: 10 * 60 * 1000, // 10 minutes + retry: 1, + refetchOnWindowFocus: false, + }, + }, +}); + +const App = () => { + return ( + + + + + + + } /> + } /> + } /> + } /> + + + + + + + + + ); +}; + +export default App; diff --git a/frontend/src/api/userService.js b/frontend/src/api/userService.js index ac4c93c..339a962 100644 --- a/frontend/src/api/userService.js +++ b/frontend/src/api/userService.js @@ -1,31 +1,33 @@ -import axios from "axios"; - -const BASE_URL = import.meta.env.VITE_API_URL; - -export const fetchUsers = async () => { - try { - const response = await axios.get(`${BASE_URL}/users`); - return response.data; - } catch (error) { - console.error("Error fetching users:", error); - throw error; - } -}; - -export const registerUser = async (userData) => { - try { - await axios.post(`${BASE_URL}/register`, userData); - } catch (error) { - console.error("Error registering user:", error); - throw error; - } -}; - -export const deleteUser = async (id) => { - try { - await axios.delete(`${BASE_URL}/users/${id}`); - } catch (error) { - console.error("Error deleting user:", error); - throw error; - } -}; +import axios from "axios"; +import { getApiUrl } from "../utils/config.js"; + +const BASE_URL = getApiUrl(); +console.log('userService BASE_URL:', BASE_URL); // Debug log + +export const fetchUsers = async () => { + try { + const response = await axios.get(`${BASE_URL}/users`); + return response.data; + } catch (error) { + console.error("Error fetching users:", error); + throw error; + } +}; + +export const registerUser = async (userData) => { + try { + await axios.post(`${BASE_URL}/register`, userData); + } catch (error) { + console.error("Error registering user:", error); + throw error; + } +}; + +export const deleteUser = async (id) => { + try { + await axios.delete(`${BASE_URL}/users/${id}`); + } catch (error) { + console.error("Error deleting user:", error); + throw error; + } +}; diff --git a/frontend/src/components/common/ErrorBoundary.css b/frontend/src/components/common/ErrorBoundary.css new file mode 100644 index 0000000..bc8230a --- /dev/null +++ b/frontend/src/components/common/ErrorBoundary.css @@ -0,0 +1,191 @@ +.error-boundary { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + padding: 2rem; +} + +.error-container { + background: white; + border-radius: 1rem; + padding: 3rem; + text-align: center; + max-width: 600px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + animation: slideIn 0.5s ease-out; +} + +.error-icon { + font-size: 4rem; + color: #ef4444; + margin-bottom: 1.5rem; + animation: pulse 2s infinite; +} + +.error-title { + font-size: 2rem; + font-weight: 700; + color: #1f2937; + margin-bottom: 1rem; +} + +.error-message { + font-size: 1.125rem; + color: #6b7280; + line-height: 1.6; + margin-bottom: 2rem; +} + +.error-actions { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; + margin-bottom: 2rem; +} + +.error-button { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + border: none; + border-radius: 0.5rem; + font-size: 1rem; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + text-decoration: none; +} + +.error-button.primary { + background: #ff8000; + color: white; +} + +.error-button.primary:hover { + background: #e67300; + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(255, 128, 0, 0.3); +} + +.error-button.secondary { + background: #f3f4f6; + color: #374151; + border: 1px solid #d1d5db; +} + +.error-button.secondary:hover { + background: #e5e7eb; + transform: translateY(-2px); +} + +.button-icon { + font-size: 1rem; +} + +.error-details { + margin-top: 2rem; + text-align: left; + border-top: 1px solid #e5e7eb; + padding-top: 1rem; +} + +.error-details summary { + cursor: pointer; + font-weight: 600; + color: #374151; + margin-bottom: 1rem; +} + +.error-details summary:hover { + color: #ff8000; +} + +.error-stack { + background: #f9fafb; + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + padding: 1rem; + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.875rem; + line-height: 1.5; + overflow-x: auto; +} + +.error-stack h4 { + margin: 0 0 0.5rem 0; + color: #374151; + font-size: 0.875rem; +} + +.error-stack pre { + margin: 0 0 1rem 0; + color: #ef4444; + white-space: pre-wrap; + word-break: break-word; +} + +/* Animations */ +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes pulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } +} + +/* Responsive Design */ +@media (max-width: 768px) { + .error-container { + padding: 2rem; + margin: 1rem; + } + + .error-title { + font-size: 1.5rem; + } + + .error-message { + font-size: 1rem; + } + + .error-actions { + flex-direction: column; + align-items: center; + } + + .error-button { + width: 100%; + max-width: 200px; + justify-content: center; + } +} + +@media (max-width: 480px) { + .error-boundary { + padding: 1rem; + } + + .error-container { + padding: 1.5rem; + } + + .error-icon { + font-size: 3rem; + } +} \ No newline at end of file diff --git a/frontend/src/components/common/ErrorBoundary.jsx b/frontend/src/components/common/ErrorBoundary.jsx new file mode 100644 index 0000000..ec3b1cf --- /dev/null +++ b/frontend/src/components/common/ErrorBoundary.jsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { FaExclamationTriangle, FaHome, FaRedo } from 'react-icons/fa'; +import './ErrorBoundary.css'; + +class ErrorBoundary extends React.Component { + constructor(props) { + super(props); + this.state = { hasError: false, error: null, errorInfo: null }; + } + + static getDerivedStateFromError(error) { + return { hasError: true }; + } + + componentDidCatch(error, errorInfo) { + this.setState({ + error: error, + errorInfo: errorInfo, + }); + + // Log error to console in development + if (process.env.NODE_ENV === 'development') { + console.error('Error caught by boundary:', error, errorInfo); + } + + // In production, you would send this to an error reporting service + // Example: Sentry.captureException(error, { extra: errorInfo }); + } + + handleReload = () => { + window.location.reload(); + }; + + handleGoHome = () => { + window.location.href = '/'; + }; + + render() { + if (this.state.hasError) { + return ( +
+
+
+ +
+

Oops! Something went wrong

+

+ We're sorry, but something unexpected happened. Our team has been notified and is working to fix the issue. +

+ +
+ + +
+ + {process.env.NODE_ENV === 'development' && this.state.error && ( +
+ Error Details (Development Only) +
+

Error:

+
{this.state.error.toString()}
+

Component Stack:

+
{this.state.errorInfo.componentStack}
+
+
+ )} +
+
+ ); + } + + return this.props.children; + } +} + +export default ErrorBoundary; \ No newline at end of file diff --git a/frontend/src/components/layout/Layout.css b/frontend/src/components/layout/Layout.css new file mode 100644 index 0000000..96948ab --- /dev/null +++ b/frontend/src/components/layout/Layout.css @@ -0,0 +1,225 @@ +/* Layout Styles */ +.layout { + min-height: 100vh; + display: flex; + flex-direction: column; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +/* Header Styles */ +.header { + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-bottom: 1px solid rgba(255, 255, 255, 0.2); + box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1); + position: sticky; + top: 0; + z-index: 1000; +} + +.header-content { + max-width: 1200px; + margin: 0 auto; + padding: 1rem 2rem; + display: flex; + justify-content: space-between; + align-items: center; +} + +.logo { + display: flex; + align-items: center; + gap: 0.75rem; +} + +.logo-icon { + font-size: 2rem; + color: #ff8000; +} + +.logo-text { + display: flex; + flex-direction: column; + font-size: 1.25rem; + font-weight: 700; + margin: 0; +} + +.logo-primary { + color: #ff8000; + font-size: 1.5rem; +} + +.logo-secondary { + color: #374151; + font-size: 0.875rem; + font-weight: 500; +} + +/* Navigation Styles */ +.navigation { + display: flex; + gap: 1rem; + align-items: center; +} + +.nav-link { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1rem; + text-decoration: none; + color: #374151; + font-weight: 500; + border-radius: 0.5rem; + transition: all 0.3s ease; + position: relative; +} + +.nav-link:hover { + background: rgba(255, 128, 0, 0.1); + color: #ff8000; + transform: translateY(-2px); +} + +.nav-link.active { + background: #ff8000; + color: white; + box-shadow: 0 4px 12px rgba(255, 128, 0, 0.3); +} + +.nav-icon { + font-size: 1.125rem; +} + +.nav-label { + font-size: 0.875rem; +} + +/* Main Content Styles */ +.main-content { + flex: 1; + padding: 2rem 0; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 0 2rem; +} + +/* Footer Styles */ +.footer { + background: rgba(0, 0, 0, 0.8); + color: white; + padding: 1.5rem 0; + margin-top: auto; +} + +.footer-content { + max-width: 1200px; + margin: 0 auto; + padding: 0 2rem; + display: flex; + justify-content: space-between; + align-items: center; +} + +.footer-links { + display: flex; + gap: 1.5rem; +} + +.footer-link { + color: #d1d5db; + text-decoration: none; + font-size: 0.875rem; + transition: color 0.3s ease; +} + +.footer-link:hover { + color: #ff8000; +} + +/* Responsive Design */ +@media (max-width: 768px) { + .header-content { + flex-direction: column; + gap: 1rem; + padding: 1rem; + } + + .navigation { + width: 100%; + justify-content: center; + flex-wrap: wrap; + } + + .nav-link { + padding: 0.5rem 0.75rem; + font-size: 0.875rem; + } + + .nav-label { + display: none; + } + + .container { + padding: 0 1rem; + } + + .footer-content { + flex-direction: column; + gap: 1rem; + text-align: center; + } + + .footer-links { + justify-content: center; + } +} + +@media (max-width: 480px) { + .logo-text { + font-size: 1rem; + } + + .logo-primary { + font-size: 1.25rem; + } + + .navigation { + gap: 0.5rem; + } + + .nav-link { + padding: 0.5rem; + } +} + +/* Animation Classes */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.layout { + animation: fadeIn 0.5s ease-out; +} + +/* Focus States for Accessibility */ +.nav-link:focus { + outline: 2px solid #ff8000; + outline-offset: 2px; +} + +.footer-link:focus { + outline: 2px solid #ff8000; + outline-offset: 2px; +} \ No newline at end of file diff --git a/frontend/src/components/layout/Layout.jsx b/frontend/src/components/layout/Layout.jsx new file mode 100644 index 0000000..b8b000e --- /dev/null +++ b/frontend/src/components/layout/Layout.jsx @@ -0,0 +1,66 @@ +import React from 'react'; +import { Link, useLocation } from 'react-router-dom'; +import { FaGraduationCap, FaUserPlus, FaUsers, FaHome } from 'react-icons/fa'; +import './Layout.css'; + +const Layout = ({ children }) => { + const location = useLocation(); + + const navigationItems = [ + { path: '/', label: 'Dashboard', icon: FaHome }, + { path: '/register', label: 'Register Student', icon: FaUserPlus }, + { path: '/students', label: 'Student List', icon: FaUsers }, + ]; + + const isActiveRoute = (path) => location.pathname === path; + + return ( +
+ {/* Header */} +
+
+
+ +

+ CLOUDBLITZ + Student Management +

+
+ +
+
+ + {/* Main Content */} +
+
+ {children} +
+
+ + {/* Footer */} + +
+ ); +}; + +export default Layout; \ No newline at end of file diff --git a/frontend/src/context/AppContext.jsx b/frontend/src/context/AppContext.jsx new file mode 100644 index 0000000..4910da9 --- /dev/null +++ b/frontend/src/context/AppContext.jsx @@ -0,0 +1,148 @@ +import React, { createContext, useContext, useReducer, useEffect } from 'react'; +import { useQueryClient } from '@tanstack/react-query'; + +// Initial state +const initialState = { + theme: 'light', + user: null, + notifications: [], + isLoading: false, + error: null, +}; + +// Action types +const ACTIONS = { + SET_THEME: 'SET_THEME', + SET_USER: 'SET_USER', + SET_LOADING: 'SET_LOADING', + SET_ERROR: 'SET_ERROR', + ADD_NOTIFICATION: 'ADD_NOTIFICATION', + REMOVE_NOTIFICATION: 'REMOVE_NOTIFICATION', + CLEAR_NOTIFICATIONS: 'CLEAR_NOTIFICATIONS', +}; + +// Reducer function +const appReducer = (state, action) => { + switch (action.type) { + case ACTIONS.SET_THEME: + return { + ...state, + theme: action.payload, + }; + case ACTIONS.SET_USER: + return { + ...state, + user: action.payload, + }; + case ACTIONS.SET_LOADING: + return { + ...state, + isLoading: action.payload, + }; + case ACTIONS.SET_ERROR: + return { + ...state, + error: action.payload, + }; + case ACTIONS.ADD_NOTIFICATION: + return { + ...state, + notifications: [...state.notifications, action.payload], + }; + case ACTIONS.REMOVE_NOTIFICATION: + return { + ...state, + notifications: state.notifications.filter( + (notification) => notification.id !== action.payload + ), + }; + case ACTIONS.CLEAR_NOTIFICATIONS: + return { + ...state, + notifications: [], + }; + default: + return state; + } +}; + +// Create context +const AppContext = createContext(); + +// Custom hook to use the context +export const useApp = () => { + const context = useContext(AppContext); + if (!context) { + throw new Error('useApp must be used within an AppProvider'); + } + return context; +}; + +// Provider component +export const AppProvider = ({ children }) => { + const [state, dispatch] = useReducer(appReducer, initialState); + const queryClient = useQueryClient(); + + // Load theme from localStorage on mount + useEffect(() => { + const savedTheme = localStorage.getItem('theme'); + if (savedTheme) { + dispatch({ type: ACTIONS.SET_THEME, payload: savedTheme }); + } + }, []); + + // Save theme to localStorage when it changes + useEffect(() => { + localStorage.setItem('theme', state.theme); + document.documentElement.setAttribute('data-theme', state.theme); + }, [state.theme]); + + // Actions + const actions = { + setTheme: (theme) => { + dispatch({ type: ACTIONS.SET_THEME, payload: theme }); + }, + setUser: (user) => { + dispatch({ type: ACTIONS.SET_USER, payload: user }); + }, + setLoading: (isLoading) => { + dispatch({ type: ACTIONS.SET_LOADING, payload: isLoading }); + }, + setError: (error) => { + dispatch({ type: ACTIONS.SET_ERROR, payload: error }); + }, + addNotification: (notification) => { + const id = Date.now().toString(); + const newNotification = { + id, + timestamp: new Date(), + ...notification, + }; + dispatch({ type: ACTIONS.ADD_NOTIFICATION, payload: newNotification }); + + // Auto-remove notification after 5 seconds + setTimeout(() => { + actions.removeNotification(id); + }, 5000); + }, + removeNotification: (id) => { + dispatch({ type: ACTIONS.REMOVE_NOTIFICATION, payload: id }); + }, + clearNotifications: () => { + dispatch({ type: ACTIONS.CLEAR_NOTIFICATIONS }); + }, + clearCache: () => { + queryClient.clear(); + }, + invalidateQueries: (queryKey) => { + queryClient.invalidateQueries({ queryKey }); + }, + }; + + const value = { + ...state, + ...actions, + }; + + return {children}; +}; \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..db61e00 --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,735 @@ +/* Global CSS Reset and Base Styles */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + line-height: 1.6; + color: #1f2937; + background: #f9fafb; +} + +code { + font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', + monospace; +} + +/* Remove default button styles */ +button { + font-family: inherit; + cursor: pointer; +} + +/* Remove default input styles */ +input, select, textarea { + font-family: inherit; +} + +/* Remove default link styles */ +a { + text-decoration: none; + color: inherit; +} + +/* Focus styles for accessibility */ +*:focus { + outline: 2px solid #ff8000; + outline-offset: 2px; +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: #f1f5f9; +} + +::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} + +/* Utility classes */ +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + +.text-center { + text-align: center; +} + +.text-left { + text-align: left; +} + +.text-right { + text-align: right; +} + +.font-bold { + font-weight: 700; +} + +.font-semibold { + font-weight: 600; +} + +.font-medium { + font-weight: 500; +} + +.text-sm { + font-size: 0.875rem; +} + +.text-base { + font-size: 1rem; +} + +.text-lg { + font-size: 1.125rem; +} + +.text-xl { + font-size: 1.25rem; +} + +.text-2xl { + font-size: 1.5rem; +} + +.text-3xl { + font-size: 1.875rem; +} + +.text-4xl { + font-size: 2.25rem; +} + +.text-gray-500 { + color: #6b7280; +} + +.text-gray-600 { + color: #4b5563; +} + +.text-gray-700 { + color: #374151; +} + +.text-gray-800 { + color: #1f2937; +} + +.text-gray-900 { + color: #111827; +} + +.text-orange-500 { + color: #ff8000; +} + +.text-orange-600 { + color: #e67300; +} + +.bg-white { + background-color: white; +} + +.bg-gray-50 { + background-color: #f9fafb; +} + +.bg-gray-100 { + background-color: #f3f4f6; +} + +.bg-orange-500 { + background-color: #ff8000; +} + +.bg-orange-600 { + background-color: #e67300; +} + +.rounded { + border-radius: 0.25rem; +} + +.rounded-md { + border-radius: 0.375rem; +} + +.rounded-lg { + border-radius: 0.5rem; +} + +.rounded-xl { + border-radius: 0.75rem; +} + +.rounded-2xl { + border-radius: 1rem; +} + +.shadow { + box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06); +} + +.shadow-md { + box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +.shadow-lg { + box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); +} + +.shadow-xl { + box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); +} + +.p-1 { + padding: 0.25rem; +} + +.p-2 { + padding: 0.5rem; +} + +.p-3 { + padding: 0.75rem; +} + +.p-4 { + padding: 1rem; +} + +.p-6 { + padding: 1.5rem; +} + +.p-8 { + padding: 2rem; +} + +.px-2 { + padding-left: 0.5rem; + padding-right: 0.5rem; +} + +.px-4 { + padding-left: 1rem; + padding-right: 1rem; +} + +.px-6 { + padding-left: 1.5rem; + padding-right: 1.5rem; +} + +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-4 { + padding-top: 1rem; + padding-bottom: 1rem; +} + +.py-6 { + padding-top: 1.5rem; + padding-bottom: 1.5rem; +} + +.m-1 { + margin: 0.25rem; +} + +.m-2 { + margin: 0.5rem; +} + +.m-4 { + margin: 1rem; +} + +.m-6 { + margin: 1.5rem; +} + +.m-8 { + margin: 2rem; +} + +.mx-auto { + margin-left: auto; + margin-right: auto; +} + +.my-2 { + margin-top: 0.5rem; + margin-bottom: 0.5rem; +} + +.my-4 { + margin-top: 1rem; + margin-bottom: 1rem; +} + +.my-6 { + margin-top: 1.5rem; + margin-bottom: 1.5rem; +} + +.mb-2 { + margin-bottom: 0.5rem; +} + +.mb-4 { + margin-bottom: 1rem; +} + +.mb-6 { + margin-bottom: 1.5rem; +} + +.mb-8 { + margin-bottom: 2rem; +} + +.mt-2 { + margin-top: 0.5rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.mt-6 { + margin-top: 1.5rem; +} + +.mt-8 { + margin-top: 2rem; +} + +.flex { + display: flex; +} + +.flex-col { + flex-direction: column; +} + +.flex-row { + flex-direction: row; +} + +.items-center { + align-items: center; +} + +.items-start { + align-items: flex-start; +} + +.items-end { + align-items: flex-end; +} + +.justify-center { + justify-content: center; +} + +.justify-between { + justify-content: space-between; +} + +.justify-start { + justify-content: flex-start; +} + +.justify-end { + justify-content: flex-end; +} + +.gap-1 { + gap: 0.25rem; +} + +.gap-2 { + gap: 0.5rem; +} + +.gap-4 { + gap: 1rem; +} + +.gap-6 { + gap: 1.5rem; +} + +.w-full { + width: 100%; +} + +.w-auto { + width: auto; +} + +.h-full { + height: 100%; +} + +.h-auto { + height: auto; +} + +.min-h-screen { + min-height: 100vh; +} + +.max-w-sm { + max-width: 24rem; +} + +.max-w-md { + max-width: 28rem; +} + +.max-w-lg { + max-width: 32rem; +} + +.max-w-xl { + max-width: 36rem; +} + +.max-w-2xl { + max-width: 42rem; +} + +.max-w-3xl { + max-width: 48rem; +} + +.max-w-4xl { + max-width: 56rem; +} + +.max-w-5xl { + max-width: 64rem; +} + +.max-w-6xl { + max-width: 72rem; +} + +.max-w-7xl { + max-width: 80rem; +} + +.hidden { + display: none; +} + +.block { + display: block; +} + +.inline-block { + display: inline-block; +} + +.inline { + display: inline; +} + +.grid { + display: grid; +} + +.grid-cols-1 { + grid-template-columns: repeat(1, minmax(0, 1fr)); +} + +.grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); +} + +.relative { + position: relative; +} + +.absolute { + position: absolute; +} + +.fixed { + position: fixed; +} + +.sticky { + position: sticky; +} + +.top-0 { + top: 0; +} + +.right-0 { + right: 0; +} + +.bottom-0 { + bottom: 0; +} + +.left-0 { + left: 0; +} + +.z-10 { + z-index: 10; +} + +.z-20 { + z-index: 20; +} + +.z-30 { + z-index: 30; +} + +.z-40 { + z-index: 40; +} + +.z-50 { + z-index: 50; +} + +.overflow-hidden { + overflow: hidden; +} + +.overflow-auto { + overflow: auto; +} + +.overflow-scroll { + overflow: scroll; +} + +.overflow-x-auto { + overflow-x: auto; +} + +.overflow-y-auto { + overflow-y: auto; +} + +.border { + border-width: 1px; +} + +.border-0 { + border-width: 0px; +} + +.border-2 { + border-width: 2px; +} + +.border-4 { + border-width: 4px; +} + +.border-gray-200 { + border-color: #e5e7eb; +} + +.border-gray-300 { + border-color: #d1d5db; +} + +.border-gray-400 { + border-color: #9ca3af; +} + +.border-orange-500 { + border-color: #ff8000; +} + +.border-orange-600 { + border-color: #e67300; +} + +.border-solid { + border-style: solid; +} + +.border-dashed { + border-style: dashed; +} + +.border-dotted { + border-style: dotted; +} + +.border-none { + border-style: none; +} + +/* Responsive utilities */ +@media (min-width: 640px) { + .sm\:block { + display: block; + } + + .sm\:hidden { + display: none; + } + + .sm\:flex { + display: flex; + } + + .sm\:grid { + display: grid; + } + + .sm\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .sm\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +@media (min-width: 768px) { + .md\:block { + display: block; + } + + .md\:hidden { + display: none; + } + + .md\:flex { + display: flex; + } + + .md\:grid { + display: grid; + } + + .md\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .md\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .md\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } +} + +@media (min-width: 1024px) { + .lg\:block { + display: block; + } + + .lg\:hidden { + display: none; + } + + .lg\:flex { + display: flex; + } + + .lg\:grid { + display: grid; + } + + .lg\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .lg\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .lg\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } +} + +@media (min-width: 1280px) { + .xl\:block { + display: block; + } + + .xl\:hidden { + display: none; + } + + .xl\:flex { + display: flex; + } + + .xl\:grid { + display: grid; + } + + .xl\:grid-cols-2 { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .xl\:grid-cols-3 { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } + + .xl\:grid-cols-4 { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } +} \ No newline at end of file diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index 59476e2..ac49628 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,12 +1,10 @@ -import { StrictMode } from "react"; -import { createRoot } from "react-dom/client"; -import App from "./App.jsx"; -// import reportWebVitals from "./reportWebVitals.js"; - -createRoot(document.getElementById("root")).render( - - - -); - -reportWebVitals(); +import { StrictMode } from "react"; +import { createRoot } from "react-dom/client"; +import App from "./App.jsx"; +import "./index.css"; + +createRoot(document.getElementById("root")).render( + + + +); diff --git a/frontend/src/pages/Dashboard.css b/frontend/src/pages/Dashboard.css new file mode 100644 index 0000000..0880e5c --- /dev/null +++ b/frontend/src/pages/Dashboard.css @@ -0,0 +1,387 @@ +.dashboard { + padding: 2rem 0; +} + +.dashboard-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; + padding: 2rem; + background: white; + border-radius: 1rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); +} + +.header-content h1 { + font-size: 2.5rem; + font-weight: 700; + color: #1f2937; + margin: 0 0 0.5rem 0; +} + +.header-content p { + font-size: 1.125rem; + color: #6b7280; + margin: 0; +} + +.cta-button { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + background: #ff8000; + color: white; + text-decoration: none; + border-radius: 0.5rem; + font-weight: 500; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(255, 128, 0, 0.3); +} + +.cta-button:hover { + background: #e67300; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(255, 128, 0, 0.4); +} + +/* Statistics Grid */ +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.stat-card { + background: white; + padding: 1.5rem; + border-radius: 1rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + display: flex; + align-items: center; + gap: 1rem; + transition: all 0.3s ease; +} + +.stat-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); +} + +.stat-icon { + width: 60px; + height: 60px; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + color: white; +} + +.stat-icon.users { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.stat-icon.recent { + background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); +} + +.stat-icon.percentage { + background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); +} + +.stat-icon.courses { + background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%); +} + +.stat-content h3 { + font-size: 2rem; + font-weight: 700; + color: #1f2937; + margin: 0 0 0.25rem 0; +} + +.stat-content p { + font-size: 0.875rem; + color: #6b7280; + margin: 0; + font-weight: 500; +} + +/* Dashboard Content */ +.dashboard-content { + display: grid; + grid-template-columns: 2fr 1fr; + gap: 2rem; +} + +.content-section { + background: white; + border-radius: 1rem; + padding: 1.5rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); +} + +.section-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1.5rem; + padding-bottom: 1rem; + border-bottom: 1px solid #e5e7eb; +} + +.section-header h2 { + font-size: 1.5rem; + font-weight: 600; + color: #1f2937; + margin: 0; +} + +.view-all-link { + color: #ff8000; + text-decoration: none; + font-weight: 500; + font-size: 0.875rem; + transition: color 0.3s ease; +} + +.view-all-link:hover { + color: #e67300; +} + +/* Students Grid */ +.students-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 1rem; +} + +.student-card { + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem; + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + transition: all 0.3s ease; +} + +.student-card:hover { + border-color: #ff8000; + box-shadow: 0 4px 12px rgba(255, 128, 0, 0.1); +} + +.student-avatar { + width: 50px; + height: 50px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 1.25rem; +} + +.student-info h4 { + font-size: 1rem; + font-weight: 600; + color: #1f2937; + margin: 0 0 0.25rem 0; +} + +.student-course { + font-size: 0.875rem; + color: #6b7280; + margin: 0 0 0.25rem 0; +} + +.student-percentage { + font-size: 0.875rem; + font-weight: 600; + color: #ff8000; + margin: 0; +} + +/* Courses List */ +.courses-list { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.course-item { + display: flex; + align-items: center; + gap: 1rem; + padding: 1rem; + border: 1px solid #e5e7eb; + border-radius: 0.5rem; + transition: all 0.3s ease; +} + +.course-item:hover { + border-color: #ff8000; + background: rgba(255, 128, 0, 0.05); +} + +.course-rank { + width: 30px; + height: 30px; + background: #ff8000; + color: white; + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-weight: 600; + font-size: 0.875rem; +} + +.course-info h4 { + font-size: 1rem; + font-weight: 600; + color: #1f2937; + margin: 0 0 0.25rem 0; +} + +.course-info p { + font-size: 0.875rem; + color: #6b7280; + margin: 0; +} + +.course-percentage { + margin-left: auto; + font-weight: 600; + color: #ff8000; + font-size: 0.875rem; +} + +/* Empty State */ +.empty-state { + text-align: center; + padding: 3rem 1rem; + color: #6b7280; +} + +.empty-icon { + font-size: 3rem; + color: #d1d5db; + margin-bottom: 1rem; +} + +.empty-state p { + font-size: 1.125rem; + margin-bottom: 1.5rem; +} + +.empty-cta { + display: inline-flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + background: #ff8000; + color: white; + text-decoration: none; + border-radius: 0.5rem; + font-weight: 500; + transition: all 0.3s ease; +} + +.empty-cta:hover { + background: #e67300; + transform: translateY(-2px); +} + +/* Loading Spinner */ +.loading-spinner { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 4rem 2rem; + color: #6b7280; +} + +.spinner { + width: 40px; + height: 40px; + border: 4px solid #e5e7eb; + border-top: 4px solid #ff8000; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 1rem; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .dashboard-content { + grid-template-columns: 1fr; + } +} + +@media (max-width: 768px) { + .dashboard-header { + flex-direction: column; + gap: 1rem; + text-align: center; + } + + .header-content h1 { + font-size: 2rem; + } + + .stats-grid { + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + } + + .students-grid { + grid-template-columns: 1fr; + } + + .stat-card { + padding: 1rem; + } + + .stat-icon { + width: 50px; + height: 50px; + font-size: 1.25rem; + } + + .stat-content h3 { + font-size: 1.5rem; + } +} + +@media (max-width: 480px) { + .dashboard { + padding: 1rem 0; + } + + .dashboard-header { + padding: 1.5rem; + } + + .header-content h1 { + font-size: 1.75rem; + } + + .stats-grid { + grid-template-columns: 1fr; + } + + .content-section { + padding: 1rem; + } +} \ No newline at end of file diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx new file mode 100644 index 0000000..5612d1b --- /dev/null +++ b/frontend/src/pages/Dashboard.jsx @@ -0,0 +1,187 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { useQuery } from '@tanstack/react-query'; +import { FaUsers, FaUserPlus, FaGraduationCap, FaChartLine } from 'react-icons/fa'; +import { fetchUsers } from '../api/userService'; +import { useApp } from '../context/AppContext'; +import './Dashboard.css'; + +const Dashboard = () => { + const { addNotification } = useApp(); + + const { data: users = [], isLoading, error } = useQuery({ + queryKey: ['users'], + queryFn: fetchUsers, + staleTime: 5 * 60 * 1000, // 5 minutes + }); + + // Calculate statistics + const stats = { + totalStudents: users.length, + recentRegistrations: users.slice(-5).length, + averagePercentage: users.length > 0 + ? Math.round(users.reduce((sum, user) => sum + parseFloat(user.percentage || 0), 0) / users.length) + : 0, + topCourses: getTopCourses(users), + }; + + function getTopCourses(users) { + const courseCount = {}; + users.forEach(user => { + courseCount[user.course] = (courseCount[user.course] || 0) + 1; + }); + return Object.entries(courseCount) + .sort(([,a], [,b]) => b - a) + .slice(0, 3) + .map(([course, count]) => ({ course, count })); + } + + const recentStudents = users.slice(-4).reverse(); + + if (isLoading) { + return ( +
+
+

Dashboard

+

Welcome to CLOUDBLITZ Student Management System

+
+
+
+

Loading dashboard data...

+
+
+ ); + } + + if (error) { + addNotification({ + type: 'error', + title: 'Error', + message: 'Failed to load dashboard data', + }); + } + + return ( +
+
+
+

Dashboard

+

Welcome to CLOUDBLITZ Student Management System

+
+ + + Register New Student + +
+ + {/* Statistics Cards */} +
+
+
+ +
+
+

{stats.totalStudents}

+

Total Students

+
+
+ +
+
+ +
+
+

{stats.recentRegistrations}

+

Recent Registrations

+
+
+ +
+
+ +
+
+

{stats.averagePercentage}%

+

Average Percentage

+
+
+ +
+
+ +
+
+

{stats.topCourses.length}

+

Active Courses

+
+
+
+ +
+ {/* Recent Students */} +
+
+

Recent Students

+ + View All Students + +
+
+ {recentStudents.length > 0 ? ( + recentStudents.map((student) => ( +
+
+ +
+
+

{student.name}

+

{student.course}

+

{student.percentage}%

+
+
+ )) + ) : ( +
+ +

No students registered yet

+ + Register First Student + +
+ )} +
+
+ + {/* Top Courses */} +
+
+

Popular Courses

+
+
+ {stats.topCourses.length > 0 ? ( + stats.topCourses.map((course, index) => ( +
+
{index + 1}
+
+

{course.course}

+

{course.count} students

+
+
+ {Math.round((course.count / stats.totalStudents) * 100)}% +
+
+ )) + ) : ( +
+ +

No course data available

+
+ )} +
+
+
+
+ ); +}; + +export default Dashboard; \ No newline at end of file diff --git a/frontend/src/pages/NotFound.css b/frontend/src/pages/NotFound.css new file mode 100644 index 0000000..9477b2b --- /dev/null +++ b/frontend/src/pages/NotFound.css @@ -0,0 +1,218 @@ +.not-found-page { + min-height: 100vh; + display: flex; + align-items: center; + justify-content: center; + padding: 2rem; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); +} + +.not-found-container { + background: white; + border-radius: 1rem; + padding: 3rem; + text-align: center; + max-width: 600px; + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); + animation: slideIn 0.5s ease-out; +} + +.not-found-icon { + font-size: 4rem; + color: #ff8000; + margin-bottom: 1.5rem; + animation: pulse 2s infinite; +} + +.not-found-title { + font-size: 6rem; + font-weight: 900; + color: #1f2937; + margin: 0 0 0.5rem 0; + line-height: 1; + background: linear-gradient(135deg, #ff8000 0%, #e67300 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; +} + +.not-found-subtitle { + font-size: 2rem; + font-weight: 700; + color: #374151; + margin: 0 0 1rem 0; +} + +.not-found-message { + font-size: 1.125rem; + color: #6b7280; + line-height: 1.6; + margin-bottom: 2rem; + max-width: 400px; + margin-left: auto; + margin-right: auto; +} + +.not-found-actions { + display: flex; + gap: 1rem; + justify-content: center; + margin-bottom: 2rem; + flex-wrap: wrap; +} + +.not-found-btn { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + border: none; + border-radius: 0.5rem; + font-size: 1rem; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + text-decoration: none; +} + +.not-found-btn.primary { + background: #ff8000; + color: white; + box-shadow: 0 4px 12px rgba(255, 128, 0, 0.3); +} + +.not-found-btn.primary:hover { + background: #e67300; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(255, 128, 0, 0.4); +} + +.not-found-btn.secondary { + background: #f3f4f6; + color: #374151; + border: 1px solid #d1d5db; +} + +.not-found-btn.secondary:hover { + background: #e5e7eb; + transform: translateY(-2px); +} + +.not-found-help { + border-top: 1px solid #e5e7eb; + padding-top: 1.5rem; +} + +.not-found-help p { + font-size: 0.875rem; + color: #6b7280; + margin: 0 0 1rem 0; +} + +.help-links { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; +} + +.help-links a { + color: #ff8000; + text-decoration: none; + font-size: 0.875rem; + font-weight: 500; + padding: 0.5rem 1rem; + border: 1px solid #ff8000; + border-radius: 0.5rem; + transition: all 0.3s ease; +} + +.help-links a:hover { + background: #ff8000; + color: white; + transform: translateY(-1px); +} + +/* Animations */ +@keyframes slideIn { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes pulse { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.05); + } +} + +/* Responsive Design */ +@media (max-width: 768px) { + .not-found-page { + padding: 1rem; + } + + .not-found-container { + padding: 2rem; + } + + .not-found-title { + font-size: 4rem; + } + + .not-found-subtitle { + font-size: 1.5rem; + } + + .not-found-message { + font-size: 1rem; + } + + .not-found-actions { + flex-direction: column; + align-items: center; + } + + .not-found-btn { + width: 100%; + max-width: 200px; + justify-content: center; + } + + .help-links { + flex-direction: column; + align-items: center; + } + + .help-links a { + width: 100%; + max-width: 200px; + text-align: center; + } +} + +@media (max-width: 480px) { + .not-found-container { + padding: 1.5rem; + } + + .not-found-title { + font-size: 3rem; + } + + .not-found-subtitle { + font-size: 1.25rem; + } + + .not-found-icon { + font-size: 3rem; + } +} \ No newline at end of file diff --git a/frontend/src/pages/NotFound.jsx b/frontend/src/pages/NotFound.jsx new file mode 100644 index 0000000..4f8cae0 --- /dev/null +++ b/frontend/src/pages/NotFound.jsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import { FaHome, FaArrowLeft, FaExclamationTriangle } from 'react-icons/fa'; +import './NotFound.css'; + +const NotFound = () => { + return ( +
+
+
+ +
+ +

404

+

Page Not Found

+ +

+ Oops! The page you're looking for doesn't exist. It might have been moved, deleted, or you entered the wrong URL. +

+ +
+ + + Go to Dashboard + + + +
+ +
+

Need help? Try these links:

+
+ Register Student + View Students + Dashboard +
+
+
+
+ ); +}; + +export default NotFound; \ No newline at end of file diff --git a/frontend/src/pages/StudentList.css b/frontend/src/pages/StudentList.css new file mode 100644 index 0000000..4dca567 --- /dev/null +++ b/frontend/src/pages/StudentList.css @@ -0,0 +1,576 @@ +.student-list-page { + padding: 2rem 0; +} + +.list-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 2rem; + padding: 2rem; + background: white; + border-radius: 1rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); +} + +.header-content { + display: flex; + align-items: center; + gap: 1rem; +} + +.header-icon { + width: 60px; + height: 60px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 1.5rem; +} + +.header-content h1 { + font-size: 2rem; + font-weight: 700; + color: #1f2937; + margin: 0 0 0.5rem 0; +} + +.header-content p { + font-size: 1.125rem; + color: #6b7280; + margin: 0; +} + +.add-button { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + background: #ff8000; + color: white; + text-decoration: none; + border-radius: 0.5rem; + font-weight: 500; + transition: all 0.3s ease; + box-shadow: 0 4px 12px rgba(255, 128, 0, 0.3); +} + +.add-button:hover { + background: #e67300; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(255, 128, 0, 0.4); +} + +/* Filters Section */ +.filters-section { + background: white; + padding: 1.5rem; + border-radius: 1rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + margin-bottom: 1.5rem; +} + +.search-box { + position: relative; + margin-bottom: 1rem; +} + +.search-icon { + position: absolute; + left: 1rem; + top: 50%; + transform: translateY(-50%); + color: #9ca3af; + font-size: 1rem; +} + +.search-input { + width: 100%; + padding: 0.75rem 1rem 0.75rem 2.5rem; + border: 2px solid #e5e7eb; + border-radius: 0.5rem; + font-size: 1rem; + transition: all 0.3s ease; +} + +.search-input:focus { + outline: none; + border-color: #ff8000; + box-shadow: 0 0 0 3px rgba(255, 128, 0, 0.1); +} + +.filter-controls { + display: flex; + gap: 1rem; + align-items: center; + flex-wrap: wrap; +} + +.filter-group { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.filter-group label { + font-weight: 500; + color: #374151; + font-size: 0.875rem; +} + +.filter-select { + padding: 0.5rem; + border: 2px solid #e5e7eb; + border-radius: 0.5rem; + font-size: 0.875rem; + background: white; + cursor: pointer; + transition: all 0.3s ease; +} + +.filter-select:focus { + outline: none; + border-color: #ff8000; + box-shadow: 0 0 0 3px rgba(255, 128, 0, 0.1); +} + +.clear-filters { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.5rem 1rem; + background: #f3f4f6; + color: #374151; + border: 1px solid #d1d5db; + border-radius: 0.5rem; + font-size: 0.875rem; + cursor: pointer; + transition: all 0.3s ease; +} + +.clear-filters:hover { + background: #e5e7eb; + transform: translateY(-1px); +} + +/* Results Summary */ +.results-summary { + background: white; + padding: 1rem 1.5rem; + border-radius: 0.5rem; + margin-bottom: 1rem; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); +} + +.results-summary p { + margin: 0; + color: #6b7280; + font-size: 0.875rem; +} + +/* Table Container */ +.table-container { + background: white; + border-radius: 1rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + overflow: hidden; + margin-bottom: 1.5rem; +} + +.students-table { + width: 100%; + border-collapse: collapse; +} + +.students-table th { + background: #f9fafb; + padding: 1rem; + text-align: left; + font-weight: 600; + color: #374151; + border-bottom: 1px solid #e5e7eb; + font-size: 0.875rem; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.students-table th.sortable { + cursor: pointer; + user-select: none; + transition: background-color 0.3s ease; +} + +.students-table th.sortable:hover { + background: #f3f4f6; +} + +.sort-indicator { + margin-left: 0.5rem; + color: #ff8000; + font-weight: bold; +} + +.students-table td { + padding: 1rem; + border-bottom: 1px solid #f3f4f6; + vertical-align: middle; +} + +.students-table tr:hover { + background: #f9fafb; +} + +.student-name { + font-weight: 600; + color: #1f2937; +} + +.student-email { + color: #6b7280; + font-size: 0.875rem; +} + +.course-badge { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 0.25rem 0.75rem; + border-radius: 1rem; + font-size: 0.75rem; + font-weight: 500; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.percentage-badge { + padding: 0.25rem 0.75rem; + border-radius: 1rem; + font-size: 0.75rem; + font-weight: 600; + text-align: center; + min-width: 60px; + display: inline-block; +} + +.percentage-badge.excellent { + background: #dcfce7; + color: #166534; +} + +.percentage-badge.good { + background: #dbeafe; + color: #1e40af; +} + +.percentage-badge.average { + background: #fef3c7; + color: #92400e; +} + +.percentage-badge.below-average { + background: #fed7aa; + color: #c2410c; +} + +.percentage-badge.poor { + background: #fee2e2; + color: #991b1b; +} + +/* Actions */ +.actions { + display: flex; + gap: 0.5rem; + justify-content: center; +} + +.action-btn { + width: 32px; + height: 32px; + border: none; + border-radius: 0.375rem; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + transition: all 0.3s ease; + font-size: 0.875rem; +} + +.action-btn.view { + background: #dbeafe; + color: #1e40af; +} + +.action-btn.view:hover { + background: #bfdbfe; + transform: translateY(-1px); +} + +.action-btn.edit { + background: #fef3c7; + color: #92400e; +} + +.action-btn.edit:hover { + background: #fde68a; + transform: translateY(-1px); +} + +.action-btn.delete { + background: #fee2e2; + color: #991b1b; +} + +.action-btn.delete:hover:not(:disabled) { + background: #fecaca; + transform: translateY(-1px); +} + +.action-btn:disabled { + opacity: 0.5; + cursor: not-allowed; + transform: none; +} + +/* Empty State */ +.no-data { + text-align: center; + padding: 3rem 1rem; +} + +.empty-state { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + color: #6b7280; +} + +.empty-icon { + font-size: 3rem; + color: #d1d5db; +} + +.empty-state p { + font-size: 1.125rem; + margin: 0; +} + +.clear-filters-btn, +.add-first-btn { + padding: 0.5rem 1rem; + border: none; + border-radius: 0.5rem; + font-size: 0.875rem; + cursor: pointer; + transition: all 0.3s ease; + text-decoration: none; + display: inline-block; +} + +.clear-filters-btn { + background: #f3f4f6; + color: #374151; + border: 1px solid #d1d5db; +} + +.clear-filters-btn:hover { + background: #e5e7eb; +} + +.add-first-btn { + background: #ff8000; + color: white; +} + +.add-first-btn:hover { + background: #e67300; + transform: translateY(-1px); +} + +/* Pagination */ +.pagination { + display: flex; + justify-content: center; + align-items: center; + gap: 1rem; + margin-top: 2rem; +} + +.pagination-btn { + padding: 0.5rem 1rem; + border: 1px solid #d1d5db; + background: white; + color: #374151; + border-radius: 0.5rem; + cursor: pointer; + transition: all 0.3s ease; + font-size: 0.875rem; +} + +.pagination-btn:hover:not(:disabled) { + background: #f3f4f6; + border-color: #9ca3af; +} + +.pagination-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.page-numbers { + display: flex; + gap: 0.25rem; +} + +.page-btn { + width: 40px; + height: 40px; + border: 1px solid #d1d5db; + background: white; + color: #374151; + border-radius: 0.5rem; + cursor: pointer; + transition: all 0.3s ease; + font-size: 0.875rem; + font-weight: 500; +} + +.page-btn:hover { + background: #f3f4f6; + border-color: #9ca3af; +} + +.page-btn.active { + background: #ff8000; + color: white; + border-color: #ff8000; +} + +.page-btn.active:hover { + background: #e67300; +} + +/* Loading and Error States */ +.loading-spinner, +.error-state { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + padding: 4rem 2rem; + color: #6b7280; +} + +.spinner { + width: 40px; + height: 40px; + border: 4px solid #e5e7eb; + border-top: 4px solid #ff8000; + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 1rem; +} + +.error-icon { + font-size: 3rem; + color: #ef4444; + margin-bottom: 1rem; +} + +.error-state h2 { + color: #1f2937; + margin: 0 0 0.5rem 0; +} + +.error-state p { + margin: 0; + text-align: center; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Responsive Design */ +@media (max-width: 1024px) { + .students-table { + font-size: 0.875rem; + } + + .students-table th, + .students-table td { + padding: 0.75rem 0.5rem; + } +} + +@media (max-width: 768px) { + .list-header { + flex-direction: column; + gap: 1rem; + text-align: center; + } + + .header-content h1 { + font-size: 1.75rem; + } + + .filter-controls { + flex-direction: column; + align-items: stretch; + } + + .filter-group { + flex-direction: column; + align-items: stretch; + } + + .table-container { + overflow-x: auto; + } + + .students-table { + min-width: 800px; + } + + .pagination { + flex-direction: column; + gap: 0.5rem; + } + + .page-numbers { + order: -1; + } +} + +@media (max-width: 480px) { + .student-list-page { + padding: 1rem 0; + } + + .list-header { + padding: 1.5rem; + } + + .header-content h1 { + font-size: 1.5rem; + } + + .filters-section { + padding: 1rem; + } + + .search-input { + font-size: 0.875rem; + } + + .action-btn { + width: 28px; + height: 28px; + font-size: 0.75rem; + } +} \ No newline at end of file diff --git a/frontend/src/pages/StudentList.jsx b/frontend/src/pages/StudentList.jsx new file mode 100644 index 0000000..89163ab --- /dev/null +++ b/frontend/src/pages/StudentList.jsx @@ -0,0 +1,360 @@ +import React, { useState, useMemo } from 'react'; +import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; +import { Link } from 'react-router-dom'; +import { FaUsers, FaSearch, FaFilter, FaTrash, FaEdit, FaEye, FaPlus } from 'react-icons/fa'; +import { fetchUsers, deleteUser } from '../api/userService'; +import { useApp } from '../context/AppContext'; +import toast from 'react-hot-toast'; +import './StudentList.css'; + +const StudentList = () => { + const queryClient = useQueryClient(); + const { addNotification } = useApp(); + + const [searchTerm, setSearchTerm] = useState(''); + const [filterCourse, setFilterCourse] = useState(''); + const [sortBy, setSortBy] = useState('name'); + const [sortOrder, setSortOrder] = useState('asc'); + const [currentPage, setCurrentPage] = useState(1); + const [itemsPerPage] = useState(10); + + // Fetch users + const { data: users = [], isLoading, error } = useQuery({ + queryKey: ['users'], + queryFn: fetchUsers, + staleTime: 5 * 60 * 1000, + }); + + // Delete user mutation + const deleteMutation = useMutation({ + mutationFn: deleteUser, + onSuccess: () => { + toast.success('Student deleted successfully'); + addNotification({ + type: 'success', + title: 'Success', + message: 'Student has been deleted successfully', + }); + queryClient.invalidateQueries({ queryKey: ['users'] }); + }, + onError: () => { + toast.error('Failed to delete student'); + addNotification({ + type: 'error', + title: 'Error', + message: 'Failed to delete student. Please try again.', + }); + }, + }); + + // Filter and sort users + const filteredAndSortedUsers = useMemo(() => { + let filtered = users.filter(user => { + const matchesSearch = user.name.toLowerCase().includes(searchTerm.toLowerCase()) || + user.email.toLowerCase().includes(searchTerm.toLowerCase()) || + user.course.toLowerCase().includes(searchTerm.toLowerCase()); + const matchesCourse = !filterCourse || user.course === filterCourse; + return matchesSearch && matchesCourse; + }); + + filtered.sort((a, b) => { + let aValue = a[sortBy]; + let bValue = b[sortBy]; + + if (sortBy === 'percentage') { + aValue = parseFloat(aValue) || 0; + bValue = parseFloat(bValue) || 0; + } else { + aValue = aValue?.toLowerCase() || ''; + bValue = bValue?.toLowerCase() || ''; + } + + if (sortOrder === 'asc') { + return aValue > bValue ? 1 : -1; + } else { + return aValue < bValue ? 1 : -1; + } + }); + + return filtered; + }, [users, searchTerm, filterCourse, sortBy, sortOrder]); + + // Pagination + const totalPages = Math.ceil(filteredAndSortedUsers.length / itemsPerPage); + const startIndex = (currentPage - 1) * itemsPerPage; + const paginatedUsers = filteredAndSortedUsers.slice(startIndex, startIndex + itemsPerPage); + + // Get unique courses for filter + const uniqueCourses = useMemo(() => { + const courses = [...new Set(users.map(user => user.course))]; + return courses.sort(); + }, [users]); + + // Handle delete + const handleDelete = (userId, userName) => { + if (window.confirm(`Are you sure you want to delete ${userName}?`)) { + deleteMutation.mutate(userId); + } + }; + + // Handle sort + const handleSort = (field) => { + if (sortBy === field) { + setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc'); + } else { + setSortBy(field); + setSortOrder('asc'); + } + }; + + // Clear filters + const clearFilters = () => { + setSearchTerm(''); + setFilterCourse(''); + setSortBy('name'); + setSortOrder('asc'); + setCurrentPage(1); + }; + + if (isLoading) { + return ( +
+
+
+

Loading students...

+
+
+ ); + } + + if (error) { + return ( +
+
+ +

Error Loading Students

+

Failed to load student data. Please try again.

+
+
+ ); + } + + return ( +
+
+
+
+ +
+
+

Student List

+

Manage and view all registered students

+
+
+ + + Add Student + +
+ + {/* Filters and Search */} +
+
+ + setSearchTerm(e.target.value)} + className="search-input" + /> +
+ +
+
+ + +
+ + +
+
+ + {/* Results Summary */} +
+

+ Showing {paginatedUsers.length} of {filteredAndSortedUsers.length} students + {searchTerm && ` matching "${searchTerm}"`} + {filterCourse && ` in ${filterCourse}`} +

+
+ + {/* Students Table */} +
+ + + + + + + + + + + + + + + {paginatedUsers.length > 0 ? ( + paginatedUsers.map((student) => ( + + + + + + + + + + + )) + ) : ( + + + + )} + +
handleSort('name')} className="sortable"> + Name + {sortBy === 'name' && ( + + {sortOrder === 'asc' ? 'โ†‘' : 'โ†“'} + + )} + handleSort('email')} className="sortable"> + Email + {sortBy === 'email' && ( + + {sortOrder === 'asc' ? 'โ†‘' : 'โ†“'} + + )} + handleSort('course')} className="sortable"> + Course + {sortBy === 'course' && ( + + {sortOrder === 'asc' ? 'โ†‘' : 'โ†“'} + + )} + Education handleSort('percentage')} className="sortable"> + Percentage + {sortBy === 'percentage' && ( + + {sortOrder === 'asc' ? 'โ†‘' : 'โ†“'} + + )} + BranchMobileActions
{student.name}{student.email} + {student.course} + {student.studentClass} + + {student.percentage}% + + {student.branch}{student.mobileNumber} + + + +
+
+ +

No students found

+ {searchTerm || filterCourse ? ( + + ) : ( + + Add First Student + + )} +
+
+
+ + {/* Pagination */} + {totalPages > 1 && ( +
+ + +
+ {Array.from({ length: totalPages }, (_, i) => i + 1).map(page => ( + + ))} +
+ + +
+ )} +
+ ); +}; + +// Helper function to get percentage class +const getPercentageClass = (percentage) => { + const num = parseFloat(percentage); + if (num >= 90) return 'excellent'; + if (num >= 80) return 'good'; + if (num >= 70) return 'average'; + if (num >= 60) return 'below-average'; + return 'poor'; +}; + +export default StudentList; \ No newline at end of file diff --git a/frontend/src/pages/StudentRegistration.css b/frontend/src/pages/StudentRegistration.css new file mode 100644 index 0000000..a4cb5d0 --- /dev/null +++ b/frontend/src/pages/StudentRegistration.css @@ -0,0 +1,299 @@ +.registration-page { + padding: 2rem 0; +} + +.registration-container { + max-width: 800px; + margin: 0 auto; + background: white; + border-radius: 1rem; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); + overflow: hidden; +} + +.registration-header { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + padding: 2rem; + display: flex; + align-items: center; + gap: 1rem; +} + +.header-icon { + width: 60px; + height: 60px; + background: rgba(255, 255, 255, 0.2); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; +} + +.header-content h1 { + font-size: 2rem; + font-weight: 700; + margin: 0 0 0.5rem 0; +} + +.header-content p { + font-size: 1.125rem; + margin: 0; + opacity: 0.9; +} + +.registration-form { + padding: 2rem; +} + +.form-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1.5rem; + margin-bottom: 2rem; +} + +.form-group { + display: flex; + flex-direction: column; +} + +.form-group.full-width { + grid-column: 1 / -1; +} + +.form-group label { + font-weight: 600; + color: #374151; + margin-bottom: 0.5rem; + font-size: 0.875rem; +} + +.form-input { + padding: 0.75rem; + border: 2px solid #e5e7eb; + border-radius: 0.5rem; + font-size: 1rem; + transition: all 0.3s ease; + background: white; +} + +.form-input:focus { + outline: none; + border-color: #ff8000; + box-shadow: 0 0 0 3px rgba(255, 128, 0, 0.1); +} + +.form-input.error { + border-color: #ef4444; + box-shadow: 0 0 0 3px rgba(239, 68, 68, 0.1); +} + +.form-input::placeholder { + color: #9ca3af; +} + +select.form-input { + cursor: pointer; + background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e"); + background-position: right 0.5rem center; + background-repeat: no-repeat; + background-size: 1.5em 1.5em; + padding-right: 2.5rem; + appearance: none; +} + +.error-message { + color: #ef4444; + font-size: 0.875rem; + margin-top: 0.25rem; + font-weight: 500; +} + +.form-actions { + display: flex; + gap: 1rem; + justify-content: flex-end; + padding-top: 1rem; + border-top: 1px solid #e5e7eb; +} + +.btn-primary, +.btn-secondary { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.75rem 1.5rem; + border: none; + border-radius: 0.5rem; + font-size: 1rem; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + text-decoration: none; +} + +.btn-primary { + background: #ff8000; + color: white; + box-shadow: 0 4px 12px rgba(255, 128, 0, 0.3); +} + +.btn-primary:hover:not(:disabled) { + background: #e67300; + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(255, 128, 0, 0.4); +} + +.btn-primary:disabled { + background: #9ca3af; + cursor: not-allowed; + transform: none; + box-shadow: none; +} + +.btn-secondary { + background: #f3f4f6; + color: #374151; + border: 1px solid #d1d5db; +} + +.btn-secondary:hover:not(:disabled) { + background: #e5e7eb; + transform: translateY(-2px); +} + +.btn-secondary:disabled { + background: #f3f4f6; + color: #9ca3af; + cursor: not-allowed; + transform: none; +} + +.spinner { + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* Responsive Design */ +@media (max-width: 768px) { + .registration-page { + padding: 1rem 0; + } + + .registration-header { + padding: 1.5rem; + flex-direction: column; + text-align: center; + } + + .header-content h1 { + font-size: 1.75rem; + } + + .registration-form { + padding: 1.5rem; + } + + .form-grid { + grid-template-columns: 1fr; + gap: 1rem; + } + + .form-actions { + flex-direction: column; + } + + .btn-primary, + .btn-secondary { + width: 100%; + justify-content: center; + } +} + +@media (max-width: 480px) { + .registration-header { + padding: 1rem; + } + + .header-content h1 { + font-size: 1.5rem; + } + + .header-content p { + font-size: 1rem; + } + + .registration-form { + padding: 1rem; + } + + .form-input { + padding: 0.625rem; + font-size: 0.875rem; + } +} + +/* Focus states for accessibility */ +.form-input:focus { + outline: 2px solid #ff8000; + outline-offset: 2px; +} + +.btn-primary:focus, +.btn-secondary:focus { + outline: 2px solid #ff8000; + outline-offset: 2px; +} + +/* Animation for form appearance */ +.registration-container { + animation: slideUp 0.5s ease-out; +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Loading state styles */ +.form-input:disabled { + background: #f9fafb; + cursor: not-allowed; +} + +/* Success state for form inputs */ +.form-input.success { + border-color: #10b981; + box-shadow: 0 0 0 3px rgba(16, 185, 129, 0.1); +} + +/* Custom scrollbar for select dropdowns */ +select.form-input::-webkit-scrollbar { + width: 8px; +} + +select.form-input::-webkit-scrollbar-track { + background: #f1f5f9; + border-radius: 4px; +} + +select.form-input::-webkit-scrollbar-thumb { + background: #cbd5e1; + border-radius: 4px; +} + +select.form-input::-webkit-scrollbar-thumb:hover { + background: #94a3b8; +} \ No newline at end of file diff --git a/frontend/src/pages/StudentRegistration.jsx b/frontend/src/pages/StudentRegistration.jsx new file mode 100644 index 0000000..d5c7c0a --- /dev/null +++ b/frontend/src/pages/StudentRegistration.jsx @@ -0,0 +1,343 @@ +import React, { useState } from 'react'; +import { useMutation, useQueryClient } from '@tanstack/react-query'; +import { useNavigate } from 'react-router-dom'; +import { FaUserPlus, FaSpinner, FaCheck } from 'react-icons/fa'; +import { registerUser } from '../api/userService'; +import { useApp } from '../context/AppContext'; +import toast from 'react-hot-toast'; +import './StudentRegistration.css'; + +const StudentRegistration = () => { + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const { addNotification } = useApp(); + + const [formData, setFormData] = useState({ + name: '', + email: '', + course: '', + studentClass: '', + percentage: '', + branch: '', + mobileNumber: '', + }); + + const [errors, setErrors] = useState({}); + const [isSubmitting, setIsSubmitting] = useState(false); + + // Course options + const courseOptions = [ + 'Computer Science', + 'Information Technology', + 'Electronics & Communication', + 'Mechanical Engineering', + 'Civil Engineering', + 'Electrical Engineering', + 'Business Administration', + 'Data Science', + 'Artificial Intelligence', + 'Cybersecurity', + ]; + + // Branch options + const branchOptions = [ + 'Computer Science', + 'Information Technology', + 'Electronics', + 'Mechanical', + 'Civil', + 'Electrical', + 'Business', + 'Data Analytics', + 'AI/ML', + 'Network Security', + ]; + + // Form validation + const validateForm = () => { + const newErrors = {}; + + if (!formData.name.trim()) { + newErrors.name = 'Name is required'; + } else if (formData.name.length < 2) { + newErrors.name = 'Name must be at least 2 characters'; + } + + if (!formData.email.trim()) { + newErrors.email = 'Email is required'; + } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) { + newErrors.email = 'Please enter a valid email address'; + } + + if (!formData.course) { + newErrors.course = 'Course is required'; + } + + if (!formData.studentClass.trim()) { + newErrors.studentClass = 'Highest Education is required'; + } + + if (!formData.percentage) { + newErrors.percentage = 'Percentage is required'; + } else if (parseFloat(formData.percentage) < 0 || parseFloat(formData.percentage) > 100) { + newErrors.percentage = 'Percentage must be between 0 and 100'; + } + + if (!formData.branch) { + newErrors.branch = 'Branch/Stream is required'; + } + + if (!formData.mobileNumber) { + newErrors.mobileNumber = 'Mobile number is required'; + } else if (!/^\d{10}$/.test(formData.mobileNumber)) { + newErrors.mobileNumber = 'Please enter a valid 10-digit mobile number'; + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + // Handle input changes + const handleInputChange = (e) => { + const { name, value } = e.target; + setFormData(prev => ({ + ...prev, + [name]: value + })); + + // Clear error when user starts typing + if (errors[name]) { + setErrors(prev => ({ + ...prev, + [name]: '' + })); + } + }; + + // Registration mutation + const registrationMutation = useMutation({ + mutationFn: registerUser, + onSuccess: (data) => { + setIsSubmitting(false); + toast.success('Student registered successfully!'); + addNotification({ + type: 'success', + title: 'Success', + message: 'Student has been registered successfully', + }); + + // Invalidate and refetch users + queryClient.invalidateQueries({ queryKey: ['users'] }); + + // Reset form + setFormData({ + name: '', + email: '', + course: '', + studentClass: '', + percentage: '', + branch: '', + mobileNumber: '', + }); + + // Navigate to students list + setTimeout(() => { + navigate('/students'); + }, 1500); + }, + onError: (error) => { + setIsSubmitting(false); + toast.error('Failed to register student. Please try again.'); + addNotification({ + type: 'error', + title: 'Error', + message: 'Failed to register student. Please try again.', + }); + }, + }); + + // Handle form submission + const handleSubmit = async (e) => { + e.preventDefault(); + + if (!validateForm()) { + toast.error('Please fix the errors in the form'); + return; + } + + setIsSubmitting(true); + registrationMutation.mutate(formData); + }; + + return ( +
+
+
+
+ +
+
+

Student Registration

+

Register a new student in the CLOUDBLITZ system

+
+
+ +
+
+ {/* Name Field */} +
+ + + {errors.name && {errors.name}} +
+ + {/* Email Field */} +
+ + + {errors.email && {errors.email}} +
+ + {/* Course Field */} +
+ + + {errors.course && {errors.course}} +
+ + {/* Highest Education Field */} +
+ + + {errors.studentClass && {errors.studentClass}} +
+ + {/* Percentage Field */} +
+ + + {errors.percentage && {errors.percentage}} +
+ + {/* Branch Field */} +
+ + + {errors.branch && {errors.branch}} +
+ + {/* Mobile Number Field */} +
+ + + {errors.mobileNumber && {errors.mobileNumber}} +
+
+ +
+ + +
+
+
+
+ ); +}; + +export default StudentRegistration; \ No newline at end of file diff --git a/frontend/src/utils/config.js b/frontend/src/utils/config.js new file mode 100644 index 0000000..ad3a91c --- /dev/null +++ b/frontend/src/utils/config.js @@ -0,0 +1,20 @@ +// Utility to get configuration values from build-time environment variables +export const getConfig = (key, defaultValue = '') => { + // Use build-time environment variables + if (import.meta.env[key]) { + return import.meta.env[key]; + } + + // Return default value + return defaultValue; +}; + +// Specific getters for common config values +export const getApiUrl = () => { + const apiUrl = getConfig('VITE_API_URL', 'http://localhost:8080/api'); + console.log('API URL:', apiUrl); // Debug log + return apiUrl; +}; + +export const getApiBaseUrl = () => getConfig('VITE_API_BASE_URL', 'http://localhost:8080'); +export const getAppTitle = () => getConfig('VITE_APP_TITLE', 'EasyCRUD Student Registration'); \ No newline at end of file diff --git a/frontend/vite.config.js b/frontend/vite.config.js index ea73791..8b0f57b 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,7 +1,7 @@ -import { defineConfig } from 'vite' -import react from '@vitejs/plugin-react' - -// https://vite.dev/config/ -export default defineConfig({ - plugins: [react()], -}) +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/setup/docker/DOCKER_SETUP.md b/setup/docker/DOCKER_SETUP.md new file mode 100644 index 0000000..fc9131a --- /dev/null +++ b/setup/docker/DOCKER_SETUP.md @@ -0,0 +1,353 @@ +# Docker Setup Guide for EasyCRUD + +This guide explains how to run the entire EasyCRUD application stack using Docker Compose. + +## ๐Ÿณ Overview + +The Docker setup includes: +- **MariaDB Database** - Persistent data storage +- **Spring Boot Backend** - REST API server +- **React Frontend** - User interface +- **Nginx Reverse Proxy** - Load balancer and SSL termination (optional) + +## ๐Ÿ“‹ Prerequisites + +- Docker Engine 20.10+ +- Docker Compose 2.0+ +- At least 4GB RAM available +- Ports 80, 3000, 8080, 3306 available + +## ๐Ÿš€ Quick Start + +### 1. Clone and Navigate +```bash +git clone +cd EasyCRUD +``` + +### 2. Start All Services +```bash +# Start all services in detached mode +docker-compose up -d + +# Or start with logs visible +docker-compose up +``` + +### 3. Access the Application +- **Frontend**: http://localhost:3000 +- **Backend API**: http://localhost:8080 +- **Database**: localhost:3306 +- **Nginx Proxy**: http://localhost:80 + +## ๐Ÿ“ Docker Files Structure + +``` +EasyCRUD/ +โ”œโ”€โ”€ docker-compose.yml # Main orchestration file +โ”œโ”€โ”€ backend/ +โ”‚ โ”œโ”€โ”€ Dockerfile # Backend container build +โ”‚ โ”œโ”€โ”€ .dockerignore # Backend build exclusions +โ”‚ โ””โ”€โ”€ database_schema.sql # Database initialization +โ”œโ”€โ”€ frontend/ +โ”‚ โ”œโ”€โ”€ Dockerfile # Frontend container build +โ”‚ โ”œโ”€โ”€ .dockerignore # Frontend build exclusions +โ”‚ โ””โ”€โ”€ nginx.conf # Frontend nginx config +โ””โ”€โ”€ nginx/ + โ””โ”€โ”€ nginx.conf # Main reverse proxy config +``` + +## ๐Ÿ”ง Service Configuration + +### Database (MariaDB) +- **Image**: mariadb:10.11 +- **Port**: 3306 +- **Database**: student_db +- **User**: easycrud_user +- **Password**: easycrud_password +- **Root Password**: rootpassword +- **Volume**: db_data (persistent storage) +- **Schema**: Auto-imported from database_schema.sql + +### Backend (Spring Boot) +- **Port**: 8080 +- **Database Connection**: jdbc:mariadb://database:3306/student_db +- **Health Check**: /actuator/health +- **Dependencies**: Database service + +### Frontend (React + Nginx) +- **Port**: 3000 +- **Build**: Multi-stage with Node.js and Nginx +- **API Proxy**: /api/* โ†’ backend:8080 +- **Health Check**: /health +- **Dependencies**: Backend service + +### Nginx Reverse Proxy (Optional) +- **Ports**: 80 (HTTP), 443 (HTTPS) +- **Load Balancing**: Frontend and Backend +- **Rate Limiting**: API endpoints +- **SSL**: Configured for production + +## ๐Ÿ› ๏ธ Docker Commands + +### Basic Operations +```bash +# Start services +docker-compose up -d + +# View logs +docker-compose logs -f + +# View specific service logs +docker-compose logs -f backend +docker-compose logs -f frontend +docker-compose logs -f database + +# Stop services +docker-compose down + +# Stop and remove volumes +docker-compose down -v + +# Rebuild and start +docker-compose up --build -d + +# View running containers +docker-compose ps +``` + +### Development Commands +```bash +# Rebuild specific service +docker-compose build backend + +# Restart specific service +docker-compose restart backend + +# Execute commands in running containers +docker-compose exec backend sh +docker-compose exec database mysql -u root -p + +# View container resources +docker stats +``` + +### Database Operations +```bash +# Connect to database +docker-compose exec database mysql -u easycrud_user -p student_db + +# Backup database +docker-compose exec database mysqldump -u root -prootpassword student_db > backup.sql + +# Restore database +docker-compose exec -T database mysql -u root -prootpassword student_db < backup.sql + +# View database logs +docker-compose logs database +``` + +## ๐Ÿ” Health Checks + +All services include health checks: + +```bash +# Check service health +docker-compose ps + +# Manual health checks +curl http://localhost:3000/health # Frontend +curl http://localhost:8080/actuator/health # Backend +curl http://localhost/health # Nginx +``` + +## ๐Ÿ“Š Monitoring + +### View Resource Usage +```bash +# Container statistics +docker stats + +# Service status +docker-compose ps + +# Log monitoring +docker-compose logs -f --tail=100 +``` + +### Performance Monitoring +```bash +# Check database performance +docker-compose exec database mysql -u root -p -e "SHOW PROCESSLIST;" + +# Check nginx access logs +docker-compose exec nginx tail -f /var/log/nginx/access.log + +# Check application logs +docker-compose logs -f backend +``` + +## ๐Ÿ”’ Security Configuration + +### Environment Variables +Create a `.env` file for production: + +```env +# Database +MYSQL_ROOT_PASSWORD=your_secure_root_password +MYSQL_DATABASE=student_db +MYSQL_USER=your_db_user +MYSQL_PASSWORD=your_secure_password + +# Backend +SPRING_PROFILES_ACTIVE=prod +SPRING_DATASOURCE_URL=jdbc:mariadb://database:3306/student_db +SPRING_DATASOURCE_USERNAME=${MYSQL_USER} +SPRING_DATASOURCE_PASSWORD=${MYSQL_PASSWORD} + +# Frontend +VITE_API_BASE_URL=https://your-domain.com +``` + +### SSL Configuration +For production HTTPS: + +1. **Generate SSL certificates** +2. **Update nginx/nginx.conf** +3. **Uncomment HTTPS server block** +4. **Set proper domain name** + +## ๐Ÿšจ Troubleshooting + +### Common Issues + +#### 1. Port Conflicts +```bash +# Check port usage +netstat -tulpn | grep :8080 +lsof -i :8080 + +# Change ports in docker-compose.yml +ports: + - "8081:8080" # Use different host port +``` + +#### 2. Database Connection Issues +```bash +# Check database status +docker-compose logs database + +# Test database connection +docker-compose exec backend curl -f http://localhost:8080/actuator/health + +# Reset database +docker-compose down -v +docker-compose up -d +``` + +#### 3. Frontend Build Issues +```bash +# Rebuild frontend +docker-compose build --no-cache frontend +docker-compose up -d frontend + +# Check build logs +docker-compose logs frontend +``` + +#### 4. Memory Issues +```bash +# Check available memory +free -h + +# Increase Docker memory limit +# In Docker Desktop: Settings โ†’ Resources โ†’ Memory +``` + +### Debug Commands +```bash +# Inspect container +docker-compose exec backend sh +docker-compose exec frontend sh + +# View container details +docker inspect easycrud-backend + +# Check network connectivity +docker-compose exec backend ping database +docker-compose exec frontend ping backend +``` + +## ๐Ÿ“ˆ Production Deployment + +### 1. Environment Setup +```bash +# Create production environment file +cp .env.example .env.prod + +# Edit production variables +nano .env.prod +``` + +### 2. SSL Certificate Setup +```bash +# Create SSL directory +mkdir -p nginx/ssl + +# Add your certificates +cp your-cert.pem nginx/ssl/cert.pem +cp your-key.pem nginx/ssl/key.pem +``` + +### 3. Production Deployment +```bash +# Use production compose file +docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d + +# Or set environment +export COMPOSE_ENV=production +docker-compose up -d +``` + +### 4. Backup Strategy +```bash +# Create backup script +#!/bin/bash +DATE=$(date +%Y%m%d_%H%M%S) +docker-compose exec database mysqldump -u root -prootpassword student_db > backup_$DATE.sql +``` + +## ๐Ÿ”„ CI/CD Integration + +### GitHub Actions Example +```yaml +name: Deploy to Production +on: + push: + branches: [main] + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Deploy with Docker Compose + run: | + docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d +``` + +## ๐Ÿ“š Additional Resources + +- [Docker Compose Documentation](https://docs.docker.com/compose/) +- [MariaDB Docker Image](https://hub.docker.com/_/mariadb) +- [Nginx Docker Image](https://hub.docker.com/_/nginx) +- [Spring Boot Docker Guide](https://spring.io/guides/gs/spring-boot-docker/) + +## ๐Ÿ†˜ Support + +For Docker-related issues: +1. Check the troubleshooting section +2. Review container logs: `docker-compose logs` +3. Verify Docker and Docker Compose versions +4. Check system resources and port availability +5. Ensure all prerequisites are met \ No newline at end of file diff --git a/setup/docker/docker-compose.dev.yml b/setup/docker/docker-compose.dev.yml new file mode 100644 index 0000000..edf9183 --- /dev/null +++ b/setup/docker/docker-compose.dev.yml @@ -0,0 +1,83 @@ +version: '3.8' + +services: + # MariaDB Database + database: + image: mariadb:10.11 + container_name: easycrud-db-dev + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: student_db + MYSQL_USER: easycrud_user + MYSQL_PASSWORD: easycrud_password + ports: + - "3306:3306" + volumes: + - db_data_dev:/var/lib/mysql + - ./backend/database_schema.sql:/docker-entrypoint-initdb.d/01-schema.sql + networks: + - easycrud-network-dev + + # Spring Boot Backend + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: easycrud-backend-dev + restart: unless-stopped + environment: + SPRING_DATASOURCE_URL: jdbc:mariadb://database:3306/student_db + SPRING_DATASOURCE_USERNAME: easycrud_user + SPRING_DATASOURCE_PASSWORD: easycrud_password + SPRING_JPA_HIBERNATE_DDL_AUTO: validate + SPRING_JPA_SHOW_SQL: true + SERVER_PORT: 8080 + ports: + - "8080:8080" + depends_on: + - database + networks: + - easycrud-network-dev + + # React Frontend + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + args: + BACKEND_HOST: 34.201.59.80 + BACKEND_PORT: 8080 + container_name: easycrud-frontend-dev + restart: unless-stopped + ports: + - "3000:3000" + depends_on: + - backend + networks: + - easycrud-network-dev + + # Nginx Reverse Proxy (Optional) + nginx: + image: nginx:alpine + container_name: easycrud-nginx-dev + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + - ./nginx/ssl:/etc/nginx/ssl + depends_on: + - frontend + - backend + networks: + - easycrud-network-dev + +volumes: + db_data_dev: + driver: local + +networks: + easycrud-network-dev: + driver: bridge \ No newline at end of file diff --git a/setup/docker/docker-compose.yml b/setup/docker/docker-compose.yml new file mode 100644 index 0000000..edf9183 --- /dev/null +++ b/setup/docker/docker-compose.yml @@ -0,0 +1,83 @@ +version: '3.8' + +services: + # MariaDB Database + database: + image: mariadb:10.11 + container_name: easycrud-db-dev + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: rootpassword + MYSQL_DATABASE: student_db + MYSQL_USER: easycrud_user + MYSQL_PASSWORD: easycrud_password + ports: + - "3306:3306" + volumes: + - db_data_dev:/var/lib/mysql + - ./backend/database_schema.sql:/docker-entrypoint-initdb.d/01-schema.sql + networks: + - easycrud-network-dev + + # Spring Boot Backend + backend: + build: + context: ./backend + dockerfile: Dockerfile + container_name: easycrud-backend-dev + restart: unless-stopped + environment: + SPRING_DATASOURCE_URL: jdbc:mariadb://database:3306/student_db + SPRING_DATASOURCE_USERNAME: easycrud_user + SPRING_DATASOURCE_PASSWORD: easycrud_password + SPRING_JPA_HIBERNATE_DDL_AUTO: validate + SPRING_JPA_SHOW_SQL: true + SERVER_PORT: 8080 + ports: + - "8080:8080" + depends_on: + - database + networks: + - easycrud-network-dev + + # React Frontend + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + args: + BACKEND_HOST: 34.201.59.80 + BACKEND_PORT: 8080 + container_name: easycrud-frontend-dev + restart: unless-stopped + ports: + - "3000:3000" + depends_on: + - backend + networks: + - easycrud-network-dev + + # Nginx Reverse Proxy (Optional) + nginx: + image: nginx:alpine + container_name: easycrud-nginx-dev + restart: unless-stopped + ports: + - "80:80" + - "443:443" + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + - ./nginx/ssl:/etc/nginx/ssl + depends_on: + - frontend + - backend + networks: + - easycrud-network-dev + +volumes: + db_data_dev: + driver: local + +networks: + easycrud-network-dev: + driver: bridge \ No newline at end of file diff --git a/setup/docker/env.example b/setup/docker/env.example new file mode 100644 index 0000000..1500a61 --- /dev/null +++ b/setup/docker/env.example @@ -0,0 +1,82 @@ +# ============================================================================= +# EasyCRUD Docker Environment Configuration +# ============================================================================= +# Copy this file to .env and update the values for your environment + +# ============================================================================= +# Database Configuration +# ============================================================================= +MYSQL_ROOT_PASSWORD=your_secure_root_password_here +MYSQL_DATABASE=student_db +MYSQL_USER=easycrud_user +MYSQL_PASSWORD=your_secure_password_here + +# ============================================================================= +# Backend Configuration +# ============================================================================= +SPRING_PROFILES_ACTIVE=prod +SPRING_DATASOURCE_URL=jdbc:mariadb://database:3306/student_db +SPRING_DATASOURCE_USERNAME=${MYSQL_USER} +SPRING_DATASOURCE_PASSWORD=${MYSQL_PASSWORD} +SPRING_JPA_HIBERNATE_DDL_AUTO=validate +SPRING_JPA_SHOW_SQL=false +SERVER_PORT=8080 + +# JVM Options for Production +JAVA_OPTS=-Xms512m -Xmx1g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+UseStringDeduplication -Djava.security.egd=file:/dev/./urandom + +# ============================================================================= +# Frontend Configuration +# ============================================================================= +VITE_API_BASE_URL=http://localhost:8080 +VITE_APP_TITLE=EasyCRUD Student Registration +NODE_ENV=production + +# ============================================================================= +# Nginx Configuration +# ============================================================================= +NGINX_SERVER_NAME=localhost +NGINX_SSL_CERT_PATH=/etc/nginx/ssl/cert.pem +NGINX_SSL_KEY_PATH=/etc/nginx/ssl/key.pem + +# ============================================================================= +# Monitoring Configuration +# ============================================================================= +GRAFANA_PASSWORD=your_grafana_password_here +PROMETHEUS_RETENTION_TIME=200h + +# ============================================================================= +# Redis Configuration (Optional) +# ============================================================================= +REDIS_PASSWORD=your_redis_password_here +REDIS_MAX_MEMORY=256mb + +# ============================================================================= +# Security Configuration +# ============================================================================= +# Generate a secure random string for JWT signing +JWT_SECRET=your_jwt_secret_key_here +# Generate a secure random string for encryption +ENCRYPTION_KEY=your_encryption_key_here + +# ============================================================================= +# Backup Configuration +# ============================================================================= +BACKUP_RETENTION_DAYS=30 +BACKUP_SCHEDULE=0 2 * * * # Daily at 2 AM + +# ============================================================================= +# Development Overrides (uncomment for development) +# ============================================================================= +# SPRING_PROFILES_ACTIVE=dev +# SPRING_JPA_SHOW_SQL=true +# VITE_API_BASE_URL=http://localhost:8080 +# NODE_ENV=development + +# ============================================================================= +# Production Overrides (uncomment for production) +# ============================================================================= +# VITE_API_BASE_URL=https://your-domain.com +# NGINX_SERVER_NAME=your-domain.com +# SPRING_PROFILES_ACTIVE=prod +# SPRING_JPA_SHOW_SQL=false \ No newline at end of file diff --git a/setup/docker/nginx/nginx.conf b/setup/docker/nginx/nginx.conf new file mode 100644 index 0000000..3b52d98 --- /dev/null +++ b/setup/docker/nginx/nginx.conf @@ -0,0 +1,133 @@ +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # Logging + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + access_log /var/log/nginx/access.log main; + error_log /var/log/nginx/error.log warn; + + # Basic settings + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + # Gzip compression + gzip on; + gzip_vary on; + gzip_min_length 1024; + gzip_proxied any; + gzip_comp_level 6; + gzip_types + text/plain + text/css + text/xml + text/javascript + application/json + application/javascript + application/xml+rss + application/atom+xml + image/svg+xml; + + # Rate limiting + limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s; + limit_req_zone $binary_remote_addr zone=login:10m rate=1r/s; + + # Upstream servers + upstream frontend { + server frontend:3000; + } + + upstream backend { + server backend:8080; + } + + # HTTP server (redirect to HTTPS in production) + server { + listen 80; + server_name localhost; + + # Security headers + add_header X-Frame-Options "SAMEORIGIN" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer-when-downgrade" always; + + # Frontend routes + location / { + proxy_pass http://frontend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 30s; + proxy_send_timeout 30s; + proxy_read_timeout 30s; + } + + # API routes with rate limiting + location /api/ { + limit_req zone=api burst=20 nodelay; + proxy_pass http://backend; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_connect_timeout 30s; + proxy_send_timeout 30s; + proxy_read_timeout 30s; + } + + # Health check endpoints + location /health { + access_log off; + return 200 "healthy\n"; + add_header Content-Type text/plain; + } + + location /api/health { + proxy_pass http://backend/actuator/health; + proxy_set_header Host $host; + } + + # Static files caching + location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + proxy_pass http://frontend; + } + } + + # HTTPS server (for production) + # Uncomment and configure SSL certificates for production use + # server { + # listen 443 ssl http2; + # server_name your-domain.com; + # + # ssl_certificate /etc/nginx/ssl/cert.pem; + # ssl_certificate_key /etc/nginx/ssl/key.pem; + # ssl_protocols TLSv1.2 TLSv1.3; + # ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384; + # ssl_prefer_server_ciphers off; + # + # # Same location blocks as HTTP server + # location / { + # proxy_pass http://frontend; + # # ... same proxy settings + # } + # + # location /api/ { + # proxy_pass http://backend; + # # ... same proxy settings + # } + # } +} \ No newline at end of file From 7c1fd95831220ad80662464e14a9200b114c0b77 Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Thu, 12 Feb 2026 16:51:18 +0530 Subject: [PATCH 09/16] Update Readme.md --- backend/Readme.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/Readme.md b/backend/Readme.md index 1d69609..ed67dac 100644 --- a/backend/Readme.md +++ b/backend/Readme.md @@ -60,8 +60,8 @@ mvn clean package Run the generated JAR file by using the following command: ```bash - -java -jar target\spring-backend-v1.jar +cd target +java -jar spring-backend-v1_filename.jar ``` The application will start and be accessible at: @@ -70,4 +70,5 @@ http://localhost:8080 ### Step 5: Keep the Application Running -To keep the application running in the background, you can use nohup or a similar method. \ No newline at end of file + +To keep the application running in the background, you can use nohup or a similar method. From 970796e2851548b6291adc2e69a7187272d15116 Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Thu, 12 Feb 2026 16:52:31 +0530 Subject: [PATCH 10/16] Update README.md --- frontend/README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/README.md b/frontend/README.md index 697698d..d4f1ef0 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -29,12 +29,10 @@ npm install ## 3. Build the React Application for Production -Update backend URL in .env file +Update backend IP in .env file ```shell vim .env - - VITE_API_URL = "http://:8080/api" ``` To build the React application for production, run: @@ -53,4 +51,5 @@ systemctl start apache2 cp -rf dist/* /var/www/html/ ``` -You can access the application on http://localhost:80 \ No newline at end of file + +You can access the application on http://localhost:80 From 477595a74bc2bd69f3c1262bef6906868d5ad0bd Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Wed, 11 Mar 2026 16:16:16 +0530 Subject: [PATCH 11/16] Create compose.yaml --- compose.yaml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 compose.yaml diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 0000000..252e606 --- /dev/null +++ b/compose.yaml @@ -0,0 +1,18 @@ +version: "3.8" + +services: + backend: + build: + context: ./backend + dockerfile: Dockerfile + ports: + - "8080:8080" + + frontend: + build: + context: ./frontend + dockerfile: Dockerfile + ports: + - "3000:3000" + depends_on: + - backend From adea21bcbe7ff86fb75a63d9a5e3a20ff5435a2b Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Wed, 11 Mar 2026 16:23:38 +0530 Subject: [PATCH 12/16] Update README.md --- README.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fcf4679..e66788d 100644 --- a/README.md +++ b/README.md @@ -55,4 +55,29 @@ EXIT; 2. DB_USER 3. DB_PASS 4. DB_PORT -5. DB_NAME \ No newline at end of file +5. DB_NAME + +--- +## Docker compose Installation +``` +apt install docker-compose -y +``` +- varify docker compose is avalibal or not + +``` +docker-compose --version +``` +## docker compose file +- Create and start containers in detach way +``` +docker-compose up -d +``` + +- Stop and remove resources +``` +docker-compose down +``` +- https://docs.docker.com/compose/intro/features-uses/ + + + From f7e124f0b37ca2e0c02d72407cb18471f182dee4 Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Wed, 11 Mar 2026 16:27:10 +0530 Subject: [PATCH 13/16] Update README.md From 9bcf7582714b4c8b9622064d5b9bed791154989b Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Wed, 11 Mar 2026 17:16:44 +0530 Subject: [PATCH 14/16] Update application.properties --- backend/src/main/resources/application.properties | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 4c00e40..6485d9d 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1 +1,8 @@ server.port=8080 + +spring.datasource.url=jdbc:mariadb://:3306/student_db?sslMode=trust +spring.datasource.username=admin +spring.datasource.password=redhat123 + +spring.jpa.hibernate.ddl-auto=update +spring.jpa.show-sql=true From 0eb1b66a8cba4ca1de4859e617ca12af22fc2732 Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Tue, 24 Mar 2026 00:29:09 +0530 Subject: [PATCH 15/16] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index e66788d..4748eb3 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,12 @@ Create a new database and user: CREATE DATABASE student_db; GRANT ALL PRIVILEGES ON springbackend.* TO 'username'@'localhost' IDENTIFIED BY 'your_password'; ``` +in this case +```sql +CREATE DATABASE student_db; +GRANT ALL PRIVILEGES ON springbackend.* TO 'admin'@'localhost' IDENTIFIED BY 'Redhat123hi'; +``` + Replace username and your_password with your desired username and password. Exit MariaDB: From af335f0a16505af9e2b4a622c2bc4e1a8bedc7f0 Mon Sep 17 00:00:00 2001 From: Mukund Deotale Date: Mon, 11 May 2026 19:06:47 +0530 Subject: [PATCH 16/16] Create tester.md --- tester.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 tester.md diff --git a/tester.md b/tester.md new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/tester.md @@ -0,0 +1 @@ +