UniVeranstaltungen is a university events management platform for creating, publishing, and managing events (bazaars, trips, conferences, workshops, gym sessions) and for handling vendor applications (bazaar participation and booth rentals). The project includes a TypeScript + Express backend and a React + Vite frontend.
The German University in Cairo (GUC) hosts numerous events throughout the academic year, including bazaars, workshops, trips, conferences, and sports activities. Managing these events manually via spreadsheets and emails was inefficient and error-prone. This platform was developed to:
- Centralize event management: Provide a single platform for the Events Office to create, edit, and manage all university events
- Streamline vendor applications: Allow external vendors to apply for bazaars and booths with an automated review workflow
- Enhance student engagement: Enable students and staff to browse, register, and receive notifications for upcoming events
- Implement gamification: Encourage participation through points, badges, streaks, and leaderboards
- Reduce administrative overhead: Automate email notifications, certificate generation, and payment processing
| Issue | Status | Description |
|---|---|---|
| Email verification delay | In Progress | Verification emails may take up to 2 minutes to arrive during peak hours |
| Mobile responsiveness | In Progress | Some dialogs need better mobile viewport handling |
| Stripe test mode only | Planned | Payment integration is currently in test mode only |
| Image upload size | Known | Large image uploads (>5MB) may timeout on slower connections |
- Backend: ✅ Builds successfully with
npm run build - Frontend: ✅ Builds successfully with
npm run build - Database: Requires MongoDB (Atlas or local instance)
- Node Version: Requires Node.js 18+
This project follows consistent coding conventions across the codebase:
- Files: PascalCase for React components (
EventsList.tsx), camelCase for utilities (authMiddleware.ts) - Variables/Functions: camelCase (
getUserById,isVerified) - Constants: UPPER_SNAKE_CASE (
JWT_SECRET,MONGO_URI) - Interfaces/Types: PascalCase with
Iprefix for models (IUser,IEvent) - React Components: PascalCase (
EventRegistrationDialog,StyledButton) - CSS Classes: Tailwind utility classes with consistent ordering
- Indentation: 2 spaces
- Quotes: Double quotes for strings
- Semicolons: Required
- TypeScript: Strict mode enabled
- Linting: ESLint with TypeScript rules
// Interface naming
interface IUser extends Document {
email: string;
firstName: string;
isVerified: boolean;
}
// Function naming
const signUpUser = async (req: Request, res: Response) => { ... }
// Constant naming
const JWT_SECRET = process.env.JWT_SECRET;
// Component naming
export function EventRegistrationDialog({ event, onClose }: Props) { ... }
- Event management: Create, edit, delete, and archive bazaars, trips, conferences, workshops, and gym sessions
- Vendor applications: Apply for bazaars and booths with document uploads; admin review workflow with approve/reject actions
- Role-based access control: Support for Admin, Events Office, Vendor, Student, Staff, TA, and Professor roles
- JWT authentication: Secure login with email verification and token blacklisting for logout
- Gamification system: Points, badges, streaks, levels, tiers (Bronze/Silver/Gold), and faculty leaderboards
- Payment processing: Stripe integration for vendor application fees (test mode)
- Wallet system: In-app wallet for event registrations and transactions
- Favorites system: Save and track favorite events across sessions
- Event registration: Register for workshops, trips, and conferences with QR code tickets
- Sports court booking: Book and manage university sports court reservations
- Clubs management: Browse and join university clubs
- Gallery system: Upload and view event photos
- Certificate generation: Auto-generate attendance certificates for events
- Notification system: Real-time notifications for event updates, application status changes
- Advanced filtering: Filter events by type, status, date, category with sorting options
- Auto-generated API documentation: Swagger/OpenAPI docs at
/api/docs
| Category | Technologies |
|---|---|
| Backend | Node.js, Express, TypeScript, Mongoose (MongoDB) |
| Frontend | React 18, TypeScript, Vite |
| Authentication | JWT (jsonwebtoken), bcryptjs |
| Validation | Joi |
| Nodemailer | |
| Payments | Stripe |
| API Docs | swagger-jsdoc, swagger-ui-express |
| Styling | Tailwind CSS, Framer Motion |
| HTTP Client | Axios |
| UI Components | Radix UI, Lucide Icons |
| State Management | React Context API |
UniVeranstaltungen/
├── server/
│ ├── src/
│ │ ├── config/ # Configuration (database, swagger)
│ │ ├── controllers/ # Route handlers
│ │ ├── middleware/ # Auth & validation middleware
│ │ ├── models/ # Mongoose schemas
│ │ ├── routes/ # Express routes
│ │ ├── scripts/ # Database scripts (resetAndPopulateDB, initializeBooths)
│ │ ├── app.ts # Express app setup
│ │ └── server.ts # Server entry point
│ ├── .env # Environment variables (not committed)
│ ├── package.json
│ └── tsconfig.json
├── client/
│ ├── src/
│ │ ├── components/ # React components (ui, common, admin, events, vendor, workshops)
│ │ ├── services/ # API service clients
│ │ ├── types/ # TypeScript type definitions
│ │ ├── utils/ # Utility functions
│ │ ├── hooks/ # Custom React hooks
│ │ ├── data/ # Mock JSON data
│ │ ├── styles/ # Global styles
│ │ ├── App.tsx # Main App component
│ │ └── main.tsx # React entry point
│ ├── public/ # Static assets
│ ├── .env # Environment variables (not committed)
│ ├── package.json
│ ├── vite.config.ts
│ └── tsconfig.json
├── client/guidelines/ # Design system & implementation guidelines
├── client/audit-fixes/ # Audit documentation and fixes
└── README.md
Prerequisites:
- Node.js 18+
- MongoDB (Atlas or local)
cd server
npm install
# create .env (see Environment Variables below)
npm run devServer default: http://localhost:4000
Important for local MongoDB: Before running the development server, initialize the database:
npm run db:resetThis script will:
- Drop all existing collections
- Create sample users (admin, professor, staff, students, etc.)
- Populate test data (bazaars, trips, conferences, workshops, booths, etc.)
- Initialize all database indexes
cd client
npm install
# create .env (see .env.example)
npm run devFrontend default: http://localhost:3000
Open Swagger UI: http://localhost:4000/api/docs
Example:
PORT=4000
MONGO_URI=mongodb+srv://<user>:<pass>@cluster.mongodb.net/<db>
MONGO_DB=<db name>
JWT_SECRET=your-32+ char secret
EMAIL_SERVICE=Gmail
EMAIL_USER=stodgytomcat561@gmail.com
EMAIL_PASS=pass
MAIL_FROM=stodgytomcat561@gmail.com
CLIENT_URL=http://localhost:3000
AbstractAPIKey=your_api_key
See client/.env.example for required variables.
POST /api/auth/registerPOST /api/auth/loginGET /api/auth/verify/:token
GET /api/upcoming-eventsGET /api/search-events
POST /api/events/bazaarsPOST /api/events/tripsPOST /api/events/conferencesPOST /api/events/gym-sessionsPATCH /api/events/{type}/{id}DELETE /api/events/{type}/{id}
GET /api/vendors/bazaars— View upcoming bazaarsPOST /api/vendors/applications/bazaar— Apply for a bazaarPOST /api/vendors/applications/booth— Apply for a boothGET /api/vendors/applications/accepted— Approved applications for an emailGET /api/vendors/applications/pending-rejected— Pending & rejected applications for an emailGET /api/vendors/my-applications— Vendor dashboard applications with filters (email, status, type, includePast)GET /api/vendors/event/:eventId— Vendors for a specific event
GET /api/admin/applicationsPATCH /api/admin/applications/bazaar/:id/statusPATCH /api/admin/applications/booth/:id/status
GET /api/health
npm run dev— Start dev server with auto-reloadnpm run db:reset— Reset database and populate with sample datanpm run build— Build TypeScript to JavaScriptnpm start— Start production server
npm run dev— Start Vite dev servernpm run build— Build for productionnpm run preview— Preview production build
// server/src/middleware/authMiddleware.ts
export const requiresAuth = async (req: AuthRequest, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ message: 'Authorization token required' });
}
const token = authHeader.split(' ')[1];
try {
// Check if the token has been blacklisted
const isBlacklisted = await BlacklistedToken.findOne({ token });
if (isBlacklisted) {
return res.status(401).json({ message: 'Token is invalid (logged out)' });
}
const decoded = jwt.verify(token, process.env.JWT_SECRET!);
req.user = decoded.user;
next();
} catch (error) {
res.status(401).json({ message: 'Invalid or expired token' });
}
};// server/src/models/User.ts
export interface IUser extends Document {
email: string;
password: string;
firstName: string;
lastName: string;
gucId: string;
role: "student" | "staff" | "ta" | "professor" | "events_officer" | "admin";
isVerified: boolean;
// Gamification fields
gamificationPoints: number; // Permanent total points earned
spendablePoints: number; // Points that can be spent on rewards
level: number; // 1-100 (calculated from points)
tier: 'bronze' | 'silver' | 'gold';
currentStreak: number; // Consecutive events attended
badges: string[]; // Array of unlocked badge IDs
}// server/src/controllers/authController.ts
const userSchema = Joi.object({
email: Joi.string()
.trim()
.email()
.required()
.regex(/^[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+@(student\.)?guc\.edu\.eg$/)
.messages({
"string.pattern.base":
"Email must be in the format firstname.lastname@guc.edu.eg",
}),
password: Joi.string().trim().min(6).required(),
firstName: Joi.string().trim().required(),
lastName: Joi.string().trim().required(),
gucId: Joi.string().trim().required(),
role: Joi.string().valid("student", "staff", "ta", "professor").required(),
});// client/hooks/useEventFilterSections.ts
export function useEventFilterSections(
filters: EventFilters,
updateFilters: (updates: Partial<EventFilters>) => void
) {
return useMemo(() => [
{
id: 'type',
title: 'Event Types',
type: 'radio' as const,
selectedValues: filters.type ? [filters.type] : [''],
onChange: (values: string[]) =>
updateFilters({ type: values[0] || undefined }),
options: [
{ id: '', label: 'All Events' },
{ id: 'workshop', label: 'Workshops' },
{ id: 'conference', label: 'Conferences' },
{ id: 'bazaar', label: 'Bazaars' },
{ id: 'trip', label: 'Trips' },
]
},
// ... more filter sections
], [filters, updateFilters]);
}// client/components/events/EventsList.tsx
function getEventStatus(
startDate: string,
endDate: string,
registrationDeadline?: string
): "open" | "upcoming" | "ongoing" | "completed" {
const now = new Date();
const eventStart = new Date(startDate);
const eventEnd = new Date(endDate);
if (now > eventEnd) return "completed";
if (now >= eventStart && now <= eventEnd) return "ongoing";
if (registrationDeadline) {
const regDeadline = new Date(registrationDeadline);
regDeadline.setHours(23, 59, 59, 999);
if (now <= regDeadline) return "open";
}
return "upcoming";
}
| Test # | Endpoint | Method | Description | Expected Result |
|---|---|---|---|---|
| 1 | /api/auth/signup/user |
POST | Register new student | 200 OK with JWT token |
| 2 | /api/auth/login |
POST | Login with valid credentials | 200 OK with JWT token |
| 3 | /api/auth/login |
POST | Login with invalid password | 401 Unauthorized |
| 4 | /api/upcoming-events |
GET | Fetch upcoming events | 200 OK with events array |
| 5 | /api/vendors/applications/bazaar |
POST | Submit bazaar application (authenticated) | 201 Created |
| 6 | /api/admin/applications |
GET | Fetch all applications (admin only) | 200 OK with applications |
| 7 | /api/health |
GET | Health check endpoint | 200 OK |
We welcome contributions to UniVeranstaltungen! Here's how you can help:
- Fork the repository on GitHub
- Clone your fork locally:
git clone https://github.com/your-username/UniVeranstaltungen.git
- Create a feature branch:
git checkout -b feature/your-feature-name
- Use TypeScript for all new code (both frontend and backend)
- Follow existing patterns for naming conventions and code structure
- Write meaningful commit messages following conventional commits format
- Test your changes before submitting a PR
- Update documentation if you add new features or change existing behavior
- Ensure your code compiles without errors (
npm run build) - Update the README.md if needed
- Create a Pull Request with a clear title and description
- Link any related issues in the PR description
- Wait for code review and address any feedback
- Code follows the existing style guide
- TypeScript types are properly defined
- No console.log statements in production code
- Environment variables are not hardcoded
- New endpoints are documented with Swagger annotations
- Use GitHub Issues to report bugs
- Include steps to reproduce the issue
- Provide environment details (OS, Node version, browser)
- Attach relevant error messages or screenshots
https://react.dev/learn https://stackoverflow.com/
- Express.js Documentation
- React Documentation
- Mongoose Documentation
- Tailwind CSS Documentation
- Stripe API Documentation
- JWT.io - JWT Debugger and Documentation
- Vite - Next Generation Frontend Tooling
- Radix UI - Unstyled, accessible components
- Framer Motion - Animation library for React
- Lucide Icons - Beautiful & consistent icons
This project is licensed under the MIT License.
This project uses the following third-party services and libraries that have their own licenses:
- Stripe - Payment processing (Apache 2.0 License)
- MongoDB - Database (Server Side Public License)
See individual package LICENSE files in node_modules for complete license information.
Made with ❤️ for the German University in Cairo