diff --git a/backend/src/events/events.controller.ts b/backend/src/events/events.controller.ts
index ccae3a8..252ac04 100644
--- a/backend/src/events/events.controller.ts
+++ b/backend/src/events/events.controller.ts
@@ -11,7 +11,12 @@ import {
UseGuards,
HttpCode,
HttpStatus,
+ UseInterceptors, // Added
+ UploadedFile, // Added
} from '@nestjs/common';
+import { FileInterceptor } from '@nestjs/platform-express'; // Added
+import { diskStorage } from 'multer'; // Added
+import { extname } from 'path'; // Added
import { EventsService } from './events.service';
import { CreateEventDto } from './dto/create-event.dto';
import { UpdateEventDto } from './dto/update-event.dto';
@@ -33,12 +38,21 @@ export class EventsController {
@Query('date_from') dateFrom?: string,
@Query('date_to') dateTo?: string,
) {
- return this.eventsService.findAllPublished({
- search,
- location,
- dateFrom,
- dateTo,
- });
+ return this.eventsService.findAllPublished({ search, location, dateFrom, dateTo });
+ }
+
+ @Get('my/events')
+ @UseGuards(JwtAuthGuard, RolesGuard)
+ @Roles(Role.ORG)
+ findMyEvents(@GetUser() user: JwtPayload) {
+ return this.eventsService.findMyEvents(user.sub);
+ }
+
+ @Get('my/user-events')
+ @UseGuards(JwtAuthGuard, RolesGuard)
+ @Roles(Role.USER)
+ findUserEvents(@GetUser() user: JwtPayload) {
+ return this.eventsService.findUserEvents(user.sub);
}
@Get(':id')
@@ -46,19 +60,32 @@ export class EventsController {
return this.eventsService.findOne(id);
}
- // Organizer
- @Post()
+ // NEW: FILE UPLOAD ENDPOINT
+ @Post(':id/upload')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ORG)
- create(@Body() dto: CreateEventDto, @GetUser() user: JwtPayload) {
- return this.eventsService.create(dto, user.sub);
+ @UseInterceptors(FileInterceptor('file', {
+ storage: diskStorage({
+ destination: './uploads/documents',
+ filename: (req, file, cb) => {
+ const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9);
+ cb(null, `${file.fieldname}-${uniqueSuffix}${extname(file.originalname)}`);
+ },
+ }),
+ }))
+ uploadFile(
+ @Param('id', ParseIntPipe) id: number,
+ @UploadedFile() file: Express.Multer.File,
+ @GetUser() user: JwtPayload,
+ ) {
+ return this.eventsService.addDocument(id, user.sub, file);
}
- @Get('my/events')
+ @Post()
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ORG)
- findMyEvents(@GetUser() user: JwtPayload) {
- return this.eventsService.findMyEvents(user.sub);
+ create(@Body() dto: CreateEventDto, @GetUser() user: JwtPayload) {
+ return this.eventsService.create(dto, user.sub);
}
@Patch(':id')
@@ -82,51 +109,28 @@ export class EventsController {
@Get(':id/participants')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ORG)
- getParticipants(
- @Param('id', ParseIntPipe) id: number,
- @GetUser() user: JwtPayload,
- ) {
+ getParticipants(@Param('id', ParseIntPipe) id: number, @GetUser() user: JwtPayload) {
return this.eventsService.getParticipants(id, user.sub);
}
- // Participant
-
@Post(':id/register')
@UseGuards(JwtAuthGuard)
- registerForEvent(
- @Param('id', ParseIntPipe) id: number,
- @GetUser() user: JwtPayload,
- ) {
+ registerForEvent(@Param('id', ParseIntPipe) id: number, @GetUser() user: JwtPayload) {
return this.eventsService.registerParticipant(id, user.sub);
}
@Delete(':id/cancel-registration')
@UseGuards(JwtAuthGuard)
@HttpCode(HttpStatus.OK)
- cancelRegistration(
- @Param('id', ParseIntPipe) id: number,
- @GetUser() user: JwtPayload,
- ) {
+ cancelRegistration(@Param('id', ParseIntPipe) id: number, @GetUser() user: JwtPayload) {
return this.eventsService.cancelRegistration(id, user.sub);
}
- @Get('my/user-events')
- @UseGuards(JwtAuthGuard, RolesGuard)
- @Roles(Role.USER)
- findUserEvents(@GetUser() user: JwtPayload) {
- return this.eventsService.findUserEvents(user.sub);
- }
-
- // Organizer OR Admin
-
@Delete(':id')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.ORG, Role.ADMIN)
@HttpCode(HttpStatus.OK)
- cancelEvent(
- @Param('id', ParseIntPipe) id: number,
- @GetUser() user: JwtPayload,
- ) {
+ cancelEvent(@Param('id', ParseIntPipe) id: number, @GetUser() user: JwtPayload) {
return this.eventsService.cancel(id, user.sub, user.role);
}
-}
+}
\ No newline at end of file
diff --git a/backend/src/events/events.module.ts b/backend/src/events/events.module.ts
index a6037c2..270be48 100644
--- a/backend/src/events/events.module.ts
+++ b/backend/src/events/events.module.ts
@@ -3,9 +3,10 @@ import { EventsService } from './events.service';
import { EventsController } from './events.controller';
import { PrismaModule } from '../prisma/prisma.module';
import { AuthModule } from '../auth/auth.module';
+import { MailModule } from '../mail/mail.module';
@Module({
- imports: [PrismaModule, AuthModule],
+ imports: [PrismaModule, AuthModule,MailModule],
providers: [EventsService],
controllers: [EventsController],
})
diff --git a/backend/src/events/events.service.ts b/backend/src/events/events.service.ts
index 9fdea44..13c8a4f 100644
--- a/backend/src/events/events.service.ts
+++ b/backend/src/events/events.service.ts
@@ -6,14 +6,18 @@ import {
ConflictException,
} from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
+import { MailService } from '../mail/mail.service';
import { CreateEventDto } from './dto/create-event.dto';
import { UpdateEventDto } from './dto/update-event.dto';
@Injectable()
export class EventsService {
- constructor(private prisma: PrismaService) {}
+ constructor(
+ private prisma: PrismaService,
+ private mailService: MailService,
+ ) {}
- // R11 — Create event (Organizer only)
+ // R11 — Create event
async create(dto: CreateEventDto, organizerId: number) {
return this.prisma.event.create({
data: {
@@ -27,7 +31,7 @@ export class EventsService {
});
}
- // R13 + R33 — List all published events with optional search, location, and date range filters
+ // R13 + R33 — List all published events
async findAllPublished(filters?: {
search?: string;
location?: string;
@@ -68,10 +72,12 @@ export class EventsService {
});
}
- // List organizer's own events (any status)
async findMyEvents(organizerId: number) {
return this.prisma.event.findMany({
- where: { organizer_id: organizerId },
+ where: {
+ organizer_id: organizerId,
+ is_cancelled: false
+ },
include: {
_count: { select: { registrations: true } },
},
@@ -79,7 +85,6 @@ export class EventsService {
});
}
- // List events the user is registered for
async findUserEvents(userId: number) {
return this.prisma.event.findMany({
where: {
@@ -103,12 +108,14 @@ export class EventsService {
orderBy: { created_at: 'desc' },
});
}
- // Get a single event by ID
+
+ // UPDATED: Added documents to include
async findOne(eventId: number) {
const event = await this.prisma.event.findUnique({
where: { event_id: eventId },
include: {
organizer: { select: { user_id: true, username: true } },
+ documents: true, // <--- ADD THIS LINE
_count: {
select: { registrations: { where: { status: 'CONFIRMED' } } },
},
@@ -118,7 +125,24 @@ export class EventsService {
return event;
}
- // R30 — Update event details (Organizer, own event only)
+ // NEW METHOD: Handle the database record for the file
+ async addDocument(eventId: number, userId: number, file: Express.Multer.File) {
+ const event = await this.findOne(eventId);
+ if (event.organizer_id !== userId)
+ throw new ForbiddenException('You can only upload files to your own events');
+
+ return this.prisma.document.create({
+ data: {
+ event_id: eventId,
+ uploaded_by: userId,
+ file_name: file.originalname,
+ file_path: `/uploads/documents/${file.filename}`,
+ file_size_kb: Math.round(file.size / 1024),
+ },
+ });
+ }
+
+ // R30 — Update event details
async update(eventId: number, dto: UpdateEventDto, userId: number) {
const event = await this.findOne(eventId);
if (event.organizer_id !== userId)
@@ -126,7 +150,7 @@ export class EventsService {
if (event.is_cancelled)
throw new BadRequestException('Cannot edit a cancelled event');
- return this.prisma.event.update({
+ const updatedEvent = await this.prisma.event.update({
where: { event_id: eventId },
data: {
...(dto.title !== undefined && { title: dto.title }),
@@ -138,23 +162,12 @@ export class EventsService {
...(dto.capacity !== undefined && { capacity: dto.capacity }),
},
});
- }
- // Publish event (Organizer, own event)
- async publish(eventId: number, userId: number) {
- const event = await this.findOne(eventId);
- if (event.organizer_id !== userId)
- throw new ForbiddenException('You can only publish your own events');
- if (event.is_cancelled)
- throw new BadRequestException('Cannot publish a cancelled event');
-
- return this.prisma.event.update({
- where: { event_id: eventId },
- data: { is_published: true },
- });
+ await this.notifyParticipants(eventId, event.title, 'update');
+ return updatedEvent;
}
- // R21 — Cancel event (Organizer cancels own, Admin cancels any)
+ // R21 — Cancel event
async cancel(eventId: number, userId: number, userRole: string) {
const event = await this.findOne(eventId);
if (event.is_cancelled)
@@ -165,19 +178,47 @@ export class EventsService {
if (!isAdmin && !isOwner)
throw new ForbiddenException('Not authorised to cancel this event');
- return this.prisma.event.update({
+ const cancelledEvent = await this.prisma.event.update({
where: { event_id: eventId },
data: { is_cancelled: true, is_published: false },
});
+
+ await this.notifyParticipants(eventId, event.title, 'cancel');
+ return cancelledEvent;
+ }
+
+ private async notifyParticipants(eventId: number, title: string, type: 'update' | 'cancel') {
+ const registrations = await this.prisma.registration.findMany({
+ where: { event_id: eventId, status: 'CONFIRMED' },
+ include: { user: { select: { email: true } } },
+ });
+
+ for (const reg of registrations) {
+ if (type === 'update') {
+ this.mailService.sendEventUpdateEmail(reg.user.email, title);
+ } else {
+ this.mailService.sendEventCancellationEmail(reg.user.email, title);
+ }
+ }
+ }
+
+ async publish(eventId: number, userId: number) {
+ const event = await this.findOne(eventId);
+ if (event.organizer_id !== userId)
+ throw new ForbiddenException('You can only publish your own events');
+ if (event.is_cancelled)
+ throw new BadRequestException('Cannot publish a cancelled event');
+
+ return this.prisma.event.update({
+ where: { event_id: eventId },
+ data: { is_published: true },
+ });
}
- // R24 — Participant list for an event (Organizer of that event only)
async getParticipants(eventId: number, userId: number) {
const event = await this.findOne(eventId);
if (event.organizer_id !== userId)
- throw new ForbiddenException(
- 'Only the organizer can view the participant list',
- );
+ throw new ForbiddenException('Only the organizer can view the participant list');
return this.prisma.registration.findMany({
where: { event_id: eventId, status: 'CONFIRMED' },
@@ -188,29 +229,23 @@ export class EventsService {
});
}
- // R14 — Register participant to event (checks R15 capacity)
async registerParticipant(eventId: number, userId: number) {
const event = await this.findOne(eventId);
-
- if (!event.is_published)
- throw new BadRequestException('Event is not published');
+ if (!event.is_published) throw new BadRequestException('Event is not published');
if (event.is_cancelled) throw new BadRequestException('Event is cancelled');
- // R15 — capacity check
const count = await this.prisma.registration.count({
where: { event_id: eventId, status: 'CONFIRMED' },
});
- if (count >= event.capacity)
- throw new BadRequestException('Event is fully booked');
+ if (count >= event.capacity) throw new BadRequestException('Event is fully booked');
- // guard against duplicate registration
const existing = await this.prisma.registration.findUnique({
where: { user_id_event_id: { user_id: userId, event_id: eventId } },
});
+
if (existing) {
if (existing.status === 'CONFIRMED')
throw new ConflictException('Already registered for this event');
- // re-register after cancellation
return this.prisma.registration.update({
where: { user_id_event_id: { user_id: userId, event_id: eventId } },
data: { status: 'CONFIRMED' },
@@ -222,16 +257,11 @@ export class EventsService {
});
}
- // R12 — Cancel own registration
async cancelRegistration(eventId: number, userId: number) {
const existing = await this.prisma.registration.findUnique({
where: { user_id_event_id: { user_id: userId, event_id: eventId } },
});
-
- // If it doesn't exist at all, that's a real 404
if (!existing) throw new NotFoundException('Registration not found');
-
- // If it's already cancelled, just return the record instead of throwing error
if (existing.status === 'CANCELLED') return existing;
return this.prisma.registration.update({
@@ -239,4 +269,4 @@ export class EventsService {
data: { status: 'CANCELLED' },
});
}
-}
+}
\ No newline at end of file
diff --git a/backend/src/mail/mail.service.ts b/backend/src/mail/mail.service.ts
index 075d61e..62d0fcd 100644
--- a/backend/src/mail/mail.service.ts
+++ b/backend/src/mail/mail.service.ts
@@ -13,7 +13,6 @@ export class MailService {
async sendPasswordResetEmail(email: string, token: string) {
const resetLink = `${process.env.FRONTEND_URL}/reset-password?token=${token}`;
-
await this.transporter.sendMail({
from: `"Event System" <${process.env.SMTP_USER}>`,
to: email,
@@ -26,4 +25,33 @@ export class MailService {
`,
});
}
-}
+
+ // NEW: Notify users of changes to an event
+ async sendEventUpdateEmail(email: string, eventTitle: string) {
+ await this.transporter.sendMail({
+ from: `"Event System" <${process.env.SMTP_USER}>`,
+ to: email,
+ subject: `Update: Changes to ${eventTitle}`,
+ html: `
+
Event Update
+ Hello! We wanted to let you know that the details for ${eventTitle} have been updated by the organizer.
+ Please log in to the dashboard to check the new location or time.
+ View Event Details
+ `,
+ });
+ }
+
+ // NEW: Notify users if an event is cancelled
+ async sendEventCancellationEmail(email: string, eventTitle: string) {
+ await this.transporter.sendMail({
+ from: `"Event System" <${process.env.SMTP_USER}>`,
+ to: email,
+ subject: `Notice: ${eventTitle} has been cancelled`,
+ html: `
+ Event Cancelled
+ We regret to inform you that ${eventTitle} has been cancelled by the organizer.
+ If you have any questions, please contact the organizer directly.
+ `,
+ });
+ }
+}
\ No newline at end of file
diff --git a/backend/uploads/documents/file-1773515185868-324838748.pdf b/backend/uploads/documents/file-1773515185868-324838748.pdf
new file mode 100644
index 0000000..b714960
Binary files /dev/null and b/backend/uploads/documents/file-1773515185868-324838748.pdf differ
diff --git a/frontend/src/app/app.routes.ts b/frontend/src/app/app.routes.ts
index 966d3ce..1f3f9dd 100644
--- a/frontend/src/app/app.routes.ts
+++ b/frontend/src/app/app.routes.ts
@@ -6,39 +6,55 @@ import { rolesGuard } from './core/guards/roles.guard';
import { ForgotPasswordComponent } from './auth/forgot-password/forgot-password';
import { ResetPasswordComponent } from './auth/reset-password/reset-password';
import { UserDashboardComponent } from './user-dashboard/user-dashboard';
+import { OrganizerDashboardComponent } from './organizer-dashboard/organizer-dashboard';
import { BrowseEventsComponent } from './events/events';
import { EventDetailsComponent } from './events/event-details/event-details';
+import { CreateEventComponent } from './events/create-event/create-event';
import { ProfileComponent } from './user-dashboard/profile/profile';
+import { ParticipantListComponent } from './events/participant-list/participant-list';
export const routes: Routes = [
- // Public routes
{ path: 'register', component: RegisterComponent },
{ path: 'login', component: LoginComponent },
{ path: 'forgot-password', component: ForgotPasswordComponent },
-
{ path: 'reset-password', component: ResetPasswordComponent },
+
+ // User Dashboard
{
path: 'user-dashboard',
component: UserDashboardComponent,
canActivate: [authGuard, rolesGuard(['USER'])],
},
- // Protected routes (canActivate: [authGuard] applied — add components as they are built)
- // Any logged-in user
+
+ // Organizer Dashboard
+ {
+ path: 'organizer-dashboard',
+ component: OrganizerDashboardComponent,
+ canActivate: [authGuard, rolesGuard(['ORG'])],
+ },
+
+ // Organizer: Create Event
+ {
+ path: 'create-event',
+ component: CreateEventComponent,
+ canActivate: [authGuard, rolesGuard(['ORG'])],
+ },
+
+ // Participant List (Organizer Only)
+ {
+ path: 'events/:id/participants',
+ component: ParticipantListComponent,
+ canActivate: [authGuard, rolesGuard(['ORG'])]
+ },
+
+ // General Protected Routes
{ path: 'events', component: BrowseEventsComponent, canActivate: [authGuard] },
{ path: 'events/:id', component: EventDetailsComponent, canActivate: [authGuard] },
{ path: 'profile', component: ProfileComponent, canActivate: [authGuard] },
- // Organizer only
- // { path: 'organizer/events', component: OrgEventListComponent, canActivate: [authGuard, rolesGuard(['ORG'])] },
- // { path: 'organizer/events/create', component: CreateEventComponent, canActivate: [authGuard, rolesGuard(['ORG'])] },
-
- // Admin only
- // { path: 'admin/users', component: AdminUsersComponent, canActivate: [authGuard, rolesGuard(['ADMIN'])] },
- // { path: 'admin/logs', component: AdminLogsComponent, canActivate: [authGuard, rolesGuard(['ADMIN'])] },
-
+ // Default Redirects
{ path: '', redirectTo: 'login', pathMatch: 'full' },
{ path: '**', redirectTo: 'login' },
];
-// Re-export guards so other modules can import from one place
-export { authGuard, rolesGuard };
+export { authGuard, rolesGuard };
\ No newline at end of file
diff --git a/frontend/src/app/auth/login/login.component.ts b/frontend/src/app/auth/login/login.component.ts
index 7081de9..190f4e7 100644
--- a/frontend/src/app/auth/login/login.component.ts
+++ b/frontend/src/app/auth/login/login.component.ts
@@ -34,9 +34,18 @@ export class LoginComponent {
next: () => {
this.message = 'Login successful! Redirecting...';
this.error = '';
- // this.cdr.detectChanges();
+
+ // The AuthService.login method uses tap() to save the token.
+ // We now extract the role from that saved token.
+ const role = this.auth.getRole();
+
setTimeout(() => {
- this.router.navigate(['/user-dashboard']);
+ // Updated to check for 'ORG' instead of 'organization'
+ if (role === 'ORG') {
+ this.router.navigate(['/organizer-dashboard']);
+ } else {
+ this.router.navigate(['/user-dashboard']);
+ }
}, 1000);
},
error: (err) => {
@@ -45,8 +54,6 @@ export class LoginComponent {
this.cdr.detectChanges();
},
});
- } else {
- this.error = 'Please enter valid email and password.';
}
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/app/core/guards/auth.guard.ts b/frontend/src/app/core/guards/auth.guard.ts
index 7f8cffa..75bd695 100644
--- a/frontend/src/app/core/guards/auth.guard.ts
+++ b/frontend/src/app/core/guards/auth.guard.ts
@@ -2,10 +2,6 @@ import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../../auth/auth.service';
-/**
- * Protects routes that require the user to be logged in.
- * Redirects to /login if no valid token is found.
- */
export const authGuard: CanActivateFn = () => {
const auth = inject(AuthService);
const router = inject(Router);
diff --git a/frontend/src/app/core/guards/roles.guard.ts b/frontend/src/app/core/guards/roles.guard.ts
index 5e3b02c..7f79d7d 100644
--- a/frontend/src/app/core/guards/roles.guard.ts
+++ b/frontend/src/app/core/guards/roles.guard.ts
@@ -1,26 +1,17 @@
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { AuthService } from '../../auth/auth.service';
+export const rolesGuard = (allowedRoles: string[]): CanActivateFn => {
+ return () => {
+ const auth = inject(AuthService);
+ const router = inject(Router);
-/**
- * Protects routes that require a specific role.
- * Usage in routes: canActivate: [authGuard, rolesGuard(['ORG'])]
- * or: canActivate: [authGuard, rolesGuard(['ADMIN'])]
- * or: canActivate: [authGuard, rolesGuard(['ORG', 'ADMIN'])]
- *
- * Always combine with authGuard — rolesGuard assumes the user is already
- * authenticated and focuses only on role validation.
- */
-export const rolesGuard = (allowedRoles: string[]): CanActivateFn => () => {
- const auth = inject(AuthService);
- const router = inject(Router);
-
- const role = auth.getRole();
-
- if (role && allowedRoles.includes(role)) {
- return true;
- }
-
- // Authenticated but wrong role — send back to home
- return router.createUrlTree(['/']);
-};
+ const role = auth.getRole();
+ console.log(`[RolesGuard] User Role: ${role} | Required: ${allowedRoles}`);
+ if (role && allowedRoles.includes(role)) {
+ return true;
+ }
+ console.warn(`[RolesGuard] Access Denied. Redirecting to login.`);
+ return router.createUrlTree(['/login']);
+ };
+};
\ No newline at end of file
diff --git a/frontend/src/app/events/create-event/create-event.html b/frontend/src/app/events/create-event/create-event.html
new file mode 100644
index 0000000..6d13065
--- /dev/null
+++ b/frontend/src/app/events/create-event/create-event.html
@@ -0,0 +1,47 @@
+
\ No newline at end of file
diff --git a/frontend/src/app/events/create-event/create-event.scss b/frontend/src/app/events/create-event/create-event.scss
new file mode 100644
index 0000000..daad78b
--- /dev/null
+++ b/frontend/src/app/events/create-event/create-event.scss
@@ -0,0 +1,71 @@
+.create-event-container {
+ padding: 40px 20px;
+ display: flex;
+ justify-content: center;
+ background: #f4f7f6;
+ min-height: 100vh;
+
+ .form-card {
+ background: white;
+ padding: 30px;
+ border-radius: 12px;
+ box-shadow: 0 10px 25px rgba(0,0,0,0.05);
+ width: 100%;
+ max-width: 600px;
+
+ header {
+ margin-bottom: 25px;
+ h2 { color: #2d3436; margin: 0; }
+ p { color: #636e72; font-size: 0.9rem; }
+ }
+ }
+
+ .form-group {
+ margin-bottom: 20px;
+ label { display: block; margin-bottom: 8px; font-weight: 600; color: #2d3436; }
+ input, textarea {
+ width: 100%;
+ padding: 12px;
+ border: 1px solid #dfe6e9;
+ border-radius: 6px;
+ font-family: inherit;
+ &:focus { outline: none; border-color: #0984e3; }
+ }
+ }
+
+ .form-row {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 20px;
+ }
+
+ .actions {
+ display: flex;
+ justify-content: flex-end;
+ gap: 15px;
+ margin-top: 30px;
+
+ button {
+ padding: 12px 24px;
+ border-radius: 6px;
+ border: none;
+ cursor: pointer;
+ font-weight: 600;
+ }
+
+ .btn-cancel { background: #dfe6e9; color: #2d3436; }
+ .btn-submit {
+ background: #0984e3; color: white;
+ &:disabled { background: #74b9ff; cursor: not-allowed; }
+ }
+ }
+
+ .error-banner {
+ color: #d63031;
+ background: #ff767522;
+ padding: 10px;
+ border-radius: 4px;
+ margin-bottom: 15px;
+ font-size: 0.85rem;
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/events/create-event/create-event.spec.ts b/frontend/src/app/events/create-event/create-event.spec.ts
new file mode 100644
index 0000000..5aa3948
--- /dev/null
+++ b/frontend/src/app/events/create-event/create-event.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { CreateEvent } from './create-event';
+
+describe('CreateEvent', () => {
+ let component: CreateEvent;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [CreateEvent],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(CreateEvent);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/events/create-event/create-event.ts b/frontend/src/app/events/create-event/create-event.ts
new file mode 100644
index 0000000..da80f0e
--- /dev/null
+++ b/frontend/src/app/events/create-event/create-event.ts
@@ -0,0 +1,49 @@
+import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms';
+import { Router, RouterModule } from '@angular/router';
+import { EventService } from '../event.service';
+
+@Component({
+ selector: 'app-create-event',
+ standalone: true,
+ imports: [CommonModule, ReactiveFormsModule, RouterModule],
+ templateUrl: './create-event.html',
+ styleUrls: ['./create-event.scss']
+})
+export class CreateEventComponent {
+ eventForm: FormGroup;
+ submitting = false;
+ errorMessage = '';
+
+ constructor(
+ private fb: FormBuilder,
+ private eventService: EventService,
+ private router: Router
+ ) {
+ this.eventForm = this.fb.group({
+ title: ['', [Validators.required, Validators.minLength(3)]],
+ description: ['', [Validators.required]],
+ location: ['', [Validators.required]],
+ event_date: ['', [Validators.required]],
+ capacity: [10, [Validators.required, Validators.min(1)]]
+ });
+ }
+
+ onSubmit() {
+ if (this.eventForm.valid) {
+ this.submitting = true;
+ this.eventService.createEvent(this.eventForm.value).subscribe({
+ next: () => {
+ alert('Event created successfully!');
+ this.router.navigate(['/organizer-dashboard']);
+ },
+ error: (err) => {
+ this.errorMessage = 'Failed to create event. Please try again.';
+ this.submitting = false;
+ console.error(err);
+ }
+ });
+ }
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/events/event-details/event-details.html b/frontend/src/app/events/event-details/event-details.html
index cf5551b..ab1cbf8 100644
--- a/frontend/src/app/events/event-details/event-details.html
+++ b/frontend/src/app/events/event-details/event-details.html
@@ -1,10 +1,13 @@
-
+
-
- Loading event details...
+
+
Loading event details...
+
- Event not found. It may have been removed.
-
+
+
Event not found. It may have been removed.
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/events/event-details/event-details.scss b/frontend/src/app/events/event-details/event-details.scss
index 7ad74cb..7d15e21 100644
--- a/frontend/src/app/events/event-details/event-details.scss
+++ b/frontend/src/app/events/event-details/event-details.scss
@@ -1,6 +1,7 @@
$primary: #6366f1;
$primary-dark: #4f46e5;
$success: #10b981;
+$danger: #ef4444; // Added for Cancel
$text-main: #1e293b;
$text-muted: #64748b;
$bg-light: #f8fafc;
@@ -46,6 +47,11 @@ $shadow:
font-size: 0.85rem;
font-weight: 700;
text-transform: uppercase;
+
+ &.cancelled {
+ background: #fee2e2;
+ color: $danger;
+ }
}
h1 {
@@ -55,6 +61,19 @@ $shadow:
line-height: 1.2;
}
+ .edit-title-input {
+ width: 100%;
+ font-size: 2.5rem;
+ font-weight: 800;
+ color: $text-main;
+ border: 2px dashed $primary;
+ border-radius: 12px;
+ padding: 0.5rem 1rem;
+ margin: 1rem 0;
+ outline: none;
+ background: $bg-light;
+ }
+
.meta-strip {
display: flex;
flex-wrap: wrap;
@@ -96,13 +115,51 @@ $shadow:
.description {
line-height: 1.8;
color: #475569;
- white-space: pre-wrap; // Respects line breaks from backend
+ white-space: pre-wrap;
font-size: 1.1rem;
}
+
+ /* --- Edit Form Styles --- */
+ .edit-fields {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+
+ .field-group {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+
+ label {
+ font-weight: 600;
+ color: $text-main;
+ font-size: 0.9rem;
+ }
+
+ .form-control {
+ padding: 0.8rem 1rem;
+ border: 1.5px solid #e2e8f0;
+ border-radius: 10px;
+ font-size: 1rem;
+ transition: all 0.2s;
+
+ &:focus {
+ outline: none;
+ border-color: $primary;
+ box-shadow: 0 0 0 3px rgba($primary, 0.1);
+ }
+ }
+
+ textarea.form-control {
+ min-height: 150px;
+ resize: vertical;
+ }
+ }
+ }
}
.registration-card {
- background: $text-main; // Dark blue/black look
+ background: $text-main;
color: $white;
padding: 2rem;
border-radius: 20px;
@@ -127,6 +184,12 @@ $shadow:
}
}
+ .action-buttons {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ }
+
button {
width: 100%;
padding: 1rem;
@@ -137,7 +200,7 @@ $shadow:
cursor: pointer;
transition: all 0.2s;
- &.btn-register {
+ &.btn-register, &.btn-edit, &.btn-save {
background: $primary;
color: white;
@@ -145,12 +208,19 @@ $shadow:
background: $primary-dark;
transform: translateY(-2px);
}
+ }
- &:disabled {
- background: #4b5563;
- cursor: not-allowed;
- opacity: 0.7;
- }
+ &.btn-save {
+ background: $success;
+ &:hover { background: darken($success, 10%); }
+ }
+
+ &.btn-cancel {
+ background: rgba($white, 0.1);
+ color: $white;
+ border: 1px solid rgba($white, 0.2);
+
+ &:hover { background: rgba($white, 0.2); }
}
&.btn-registered {
@@ -159,6 +229,11 @@ $shadow:
border: 1px solid $success;
cursor: default;
}
+
+ &:disabled {
+ opacity: 0.5;
+ cursor: not-allowed;
+ }
}
}
}
@@ -169,4 +244,4 @@ $shadow:
font-size: 1.2rem;
color: $text-muted;
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/app/events/event-details/event-details.ts b/frontend/src/app/events/event-details/event-details.ts
index 045da33..dec9133 100644
--- a/frontend/src/app/events/event-details/event-details.ts
+++ b/frontend/src/app/events/event-details/event-details.ts
@@ -1,14 +1,15 @@
-// event-details.component.ts
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
-import { ActivatedRoute, RouterModule } from '@angular/router';
+import { ActivatedRoute, RouterModule, Router } from '@angular/router';
import { EventService } from '../../events/event.service';
import { AuthService } from '../../auth/auth.service';
import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { HttpClient } from '@angular/common/http'; // Added
@Component({
selector: 'app-event-details',
standalone: true,
- imports: [CommonModule, RouterModule],
+ imports: [CommonModule, RouterModule, FormsModule],
templateUrl: './event-details.html',
styleUrls: ['./event-details.scss'],
})
@@ -17,55 +18,98 @@ export class EventDetailsComponent implements OnInit {
myEvents: any[] = [];
loading = true;
isProcessing = false;
+ userRole: string | null = null;
+ selectedFile: File | null = null; // Added
+
+ isEditing = false;
+ editForm: any = {};
constructor(
private route: ActivatedRoute,
+ private router: Router,
private eventService: EventService,
private auth: AuthService,
private cdr: ChangeDetectorRef,
+ private http: HttpClient // Injected
) {}
ngOnInit() {
+ this.userRole = this.auth.getRole();
const id = this.route.snapshot.paramMap.get('id');
- if (id) {
- this.loadData(id);
- }
+ if (id) this.loadData(id);
}
loadData(eventId: string) {
- // We use "forkJoin" or simply subscribe to both
this.eventService.getEventById(eventId).subscribe((data) => {
this.event = data;
this.loading = false;
this.cdr.detectChanges();
});
+ if (this.userRole === 'USER') {
+ this.auth.getUserEvents().subscribe((events: any) => {
+ this.myEvents = events;
+ this.cdr.detectChanges();
+ });
+ }
+ }
- // Fetch user's events to check registration status
- this.auth.getUserEvents().subscribe((events: any) => {
- this.myEvents = events;
- this.cdr.detectChanges();
+ onFileSelected(event: any) {
+ this.selectedFile = event.target.files[0];
+ }
+
+ startEdit() {
+ this.isEditing = true;
+ this.editForm = { ...this.event };
+ if (this.editForm.event_date) {
+ this.editForm.event_date = new Date(this.editForm.event_date).toISOString().split('T')[0];
+ }
+ }
+
+ cancelEdit() {
+ this.isEditing = false;
+ this.selectedFile = null;
+ }
+
+ saveEdit() {
+ this.isProcessing = true;
+ this.eventService.updateEvent(this.event.event_id, this.editForm).subscribe({
+ next: (updated) => {
+ if (this.selectedFile) {
+ const formData = new FormData();
+ formData.append('file', this.selectedFile);
+ this.http.post(`http://localhost:3000/api/events/${this.event.event_id}/upload`, formData)
+ .subscribe({
+ next: () => this.finishSave(),
+ error: () => { alert('Details saved, but file upload failed.'); this.finishSave(); }
+ });
+ } else {
+ this.finishSave();
+ }
+ },
+ error: () => { alert('Update failed'); this.isProcessing = false; }
});
}
- // Check if the current event ID is in the user's list
+ finishSave() {
+ this.isProcessing = false;
+ this.isEditing = false;
+ this.router.navigate(['/organizer-dashboard']);
+ }
+
isAlreadyRegistered(): boolean {
- if (!this.event || !this.myEvents) return false;
- return this.myEvents.some((e) => e.event_id === this.event.event_id);
+ return this.myEvents.some((e) => e.event_id === this.event?.event_id);
}
register() {
this.isProcessing = true;
- this.eventService.registerForEvent(this.event.event_id).subscribe({
- next: () => {
- alert('Registration Successful!');
- this.loadData(this.event.event_id.toString()); // Refresh status
- this.isProcessing = false;
- this.cdr.detectChanges();
- },
- error: () => {
- this.isProcessing = false;
- this.cdr.detectChanges();
- },
+ this.eventService.registerForEvent(this.event.event_id).subscribe(() => {
+ alert('Registered!');
+ this.loadData(this.event.event_id.toString());
+ this.isProcessing = false;
});
}
-}
+
+ goBack() {
+ this.router.navigate([this.userRole === 'ORG' ? '/organizer-dashboard' : '/user-dashboard']);
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/events/event.service.ts b/frontend/src/app/events/event.service.ts
index 0d887bf..037fa96 100644
--- a/frontend/src/app/events/event.service.ts
+++ b/frontend/src/app/events/event.service.ts
@@ -1,17 +1,15 @@
-// event.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
@Injectable({ providedIn: 'root' })
export class EventService {
- private apiUrl = 'http://localhost:3000/api/events'; // Match your NestJS route
+ private apiUrl = 'http://localhost:3000/api/events';
constructor(private http: HttpClient) {}
getPublishedEvents(filters: any): Observable
{
let params = new HttpParams();
-
if (filters.search) params = params.append('search', filters.search);
if (filters.location) params = params.append('location', filters.location);
if (filters.dateFrom) params = params.append('date_from', filters.dateFrom);
@@ -20,6 +18,23 @@ export class EventService {
return this.http.get(this.apiUrl, { params });
}
+ getOrgEvents(): Observable {
+ return this.http.get(`${this.apiUrl}/my/events`);
+ }
+
+ createEvent(eventData: any): Observable {
+ return this.http.post(this.apiUrl, eventData);
+ }
+
+ //Method to update existing events
+ updateEvent(eventId: number, eventData: any): Observable {
+ return this.http.patch(`${this.apiUrl}/${eventId}`, eventData);
+ }
+
+ deleteEvent(eventId: number): Observable {
+ return this.http.delete(`${this.apiUrl}/${eventId}`);
+ }
+
getEventById(id: string): Observable {
return this.http.get(`${this.apiUrl}/${id}`);
}
@@ -27,4 +42,8 @@ export class EventService {
registerForEvent(eventId: number): Observable {
return this.http.post(`${this.apiUrl}/${eventId}/register`, { event_id: eventId });
}
-}
+
+ getParticipants(eventId: number) {
+ return this.http.get(`http://localhost:3000/api/events/${eventId}/participants`);
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/events/events.html b/frontend/src/app/events/events.html
index 243f7dd..5ed21c5 100644
--- a/frontend/src/app/events/events.html
+++ b/frontend/src/app/events/events.html
@@ -1,33 +1,14 @@
-
-
+
0; else empty">
{{ event.title }}
📍 {{ event.location }}
📅 {{ event.event_date | date: 'fullDate' }}
-
-
@@ -36,4 +17,4 @@
{{ event.title }}
No published events found.
-
+
\ No newline at end of file
diff --git a/frontend/src/app/events/events.ts b/frontend/src/app/events/events.ts
index 8c3fe67..4ad23f7 100644
--- a/frontend/src/app/events/events.ts
+++ b/frontend/src/app/events/events.ts
@@ -1,8 +1,10 @@
import { ChangeDetectorRef, Component, OnInit } from '@angular/core';
import { EventService } from './event.service';
-import { RouterModule } from '@angular/router';
+import { Router, RouterModule } from '@angular/router';
+import { AuthService } from '../auth/auth.service';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
+
@Component({
selector: 'app-browse-events',
standalone: true,
@@ -12,16 +14,12 @@ import { FormsModule } from '@angular/forms';
})
export class BrowseEventsComponent implements OnInit {
events: any[] = [];
-
- searchFilters = {
- search: '',
- location: '',
- dateFrom: '',
- dateTo: '',
- };
+ searchFilters = { search: '', location: '', dateFrom: '', dateTo: '' };
constructor(
private eventService: EventService,
+ private auth: AuthService,
+ private router: Router,
private cdr: ChangeDetectorRef,
) {}
@@ -29,12 +27,20 @@ export class BrowseEventsComponent implements OnInit {
this.fetchEvents();
}
+ backToDashboard() {
+ const role = this.auth.getRole();
+ if (role === 'ORG') {
+ this.router.navigate(['/organizer-dashboard']);
+ } else {
+ this.router.navigate(['/user-dashboard']);
+ }
+ }
+
fetchEvents() {
this.eventService.getPublishedEvents(this.searchFilters).subscribe({
next: (data) => {
this.events = data;
this.cdr.detectChanges();
- //console.log('Events loaded:', data);
},
error: (err) => {
console.error('Error fetching events:', err);
@@ -43,9 +49,8 @@ export class BrowseEventsComponent implements OnInit {
});
}
- // Called on every keystroke or when 'Search' button is clicked
onFilterChange() {
this.fetchEvents();
this.cdr.detectChanges();
}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/app/events/participant-list/participant-list.html b/frontend/src/app/events/participant-list/participant-list.html
new file mode 100644
index 0000000..22c266d
--- /dev/null
+++ b/frontend/src/app/events/participant-list/participant-list.html
@@ -0,0 +1,31 @@
+
+
Participant List
+
+
+
Loading participants...
+
+
0" class="table-container">
+
+
+
+ | # |
+ Username |
+ Email |
+ Registration Date |
+
+
+
+
+ | {{ i + 1 }} |
+ {{ reg.user.username }} |
+ {{ reg.user.email }} |
+ {{ reg.registered_at | date:'short' }} |
+
+
+
+
+
+
+ No participants have registered for this event yet.
+
+
\ No newline at end of file
diff --git a/frontend/src/app/events/participant-list/participant-list.scss b/frontend/src/app/events/participant-list/participant-list.scss
new file mode 100644
index 0000000..e69de29
diff --git a/frontend/src/app/events/participant-list/participant-list.spec.ts b/frontend/src/app/events/participant-list/participant-list.spec.ts
new file mode 100644
index 0000000..9bdb3fa
--- /dev/null
+++ b/frontend/src/app/events/participant-list/participant-list.spec.ts
@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { ParticipantList } from './participant-list';
+
+describe('ParticipantList', () => {
+ let component: ParticipantList;
+ let fixture: ComponentFixture;
+
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [ParticipantList],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ParticipantList);
+ component = fixture.componentInstance;
+ await fixture.whenStable();
+ });
+
+ it('should create', () => {
+ expect(component).toBeTruthy();
+ });
+});
diff --git a/frontend/src/app/events/participant-list/participant-list.ts b/frontend/src/app/events/participant-list/participant-list.ts
new file mode 100644
index 0000000..5b2e5ee
--- /dev/null
+++ b/frontend/src/app/events/participant-list/participant-list.ts
@@ -0,0 +1,43 @@
+import { Component, OnInit } from '@angular/core';
+import { ActivatedRoute, RouterModule } from '@angular/router';
+import { EventService } from '../event.service';
+import { CommonModule } from '@angular/common';
+
+@Component({
+ selector: 'app-participant-list',
+ standalone: true,
+ imports: [CommonModule, RouterModule],
+ templateUrl: './participant-list.html',
+ styleUrls: ['./participant-list.scss']
+})
+export class ParticipantListComponent implements OnInit {
+ participants: any[] = [];
+ eventId: number = 0;
+ loading = true;
+
+ constructor(
+ private route: ActivatedRoute,
+ private eventService: EventService
+ ) {}
+
+ ngOnInit() {
+ const id = this.route.snapshot.paramMap.get('id');
+ if (id) {
+ this.eventId = +id;
+ this.loadParticipants();
+ }
+ }
+
+ loadParticipants() {
+ this.eventService.getParticipants(this.eventId).subscribe({
+ next: (data) => {
+ this.participants = data;
+ this.loading = false;
+ },
+ error: (err) => {
+ console.error('Failed to load participants', err);
+ this.loading = false;
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/organizer-dashboard/organizer-dashboard.html b/frontend/src/app/organizer-dashboard/organizer-dashboard.html
new file mode 100644
index 0000000..5076379
--- /dev/null
+++ b/frontend/src/app/organizer-dashboard/organizer-dashboard.html
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+ Total Events
+ {{ myHostedEvents.length }}
+
+
+
+ 0; else noEvents" class="event-list">
+
+
+ {{ event.title }}
+ 📍 {{ event.location }}
+ 📅 {{ event.event_date | date:'mediumDate' }}
+
+
+
+
+ Registrations
+ {{ event._count?.registrations || 0 }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
You haven't created any events yet.
+
+
+
+
+
+
+
+
+
+
Initializing Dashboard...
+
+
\ No newline at end of file
diff --git a/frontend/src/app/organizer-dashboard/organizer-dashboard.scss b/frontend/src/app/organizer-dashboard/organizer-dashboard.scss
new file mode 100644
index 0000000..ee47aa2
--- /dev/null
+++ b/frontend/src/app/organizer-dashboard/organizer-dashboard.scss
@@ -0,0 +1,110 @@
+@use '../user-dashboard/user-dashboard.scss';
+
+.org-theme {
+ background: linear-gradient(135deg, #1e293b 0%, #334155 100%) !important;
+}
+
+.role-badge {
+ background: #f59e0b;
+ color: #fff;
+ padding: 2px 8px;
+ border-radius: 4px;
+ font-size: 0.7rem;
+ font-weight: bold;
+}
+
+.section-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-bottom: 2rem;
+}
+
+.btn-create {
+ background: #10b981;
+ color: white;
+ border: none;
+ padding: 0.8rem 1.2rem;
+ border-radius: 8px;
+ font-weight: 600;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ transition: background 0.2s ease;
+
+ &:hover { background: #059669; }
+}
+
+.stats-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+ gap: 1rem;
+ margin-bottom: 2rem;
+
+ .stat-card {
+ background: #f1f5f9;
+ padding: 1.5rem;
+ border-radius: 12px;
+ display: flex;
+ flex-direction: column;
+
+ .label { color: #64748b; font-size: 0.9rem; }
+ .value { font-size: 1.8rem; font-weight: 800; color: #1e293b; }
+ }
+}
+
+.event-actions {
+ display: flex;
+ align-items: center;
+ gap: 20px;
+
+ .attendance {
+ text-align: center;
+ margin-right: 10px;
+ strong { display: block; font-size: 1.2rem; color: #6366f1; }
+ small { color: #64748b; text-transform: uppercase; font-size: 0.7rem; letter-spacing: 0.5px; }
+ }
+
+ .button-group {
+ display: flex;
+ gap: 10px;
+ }
+
+ button {
+ padding: 0.6rem 1rem;
+ border-radius: 6px;
+ font-weight: 600;
+ cursor: pointer;
+ border: none;
+ transition: all 0.2s ease;
+ font-size: 0.85rem;
+ }
+
+ .btn-edit {
+ background: #6366f1;
+ color: white;
+ &:hover { background: #4f46e5; transform: translateY(-1px); }
+ }
+
+ .btn-delete {
+ background: #ef4444;
+ color: white;
+ &:hover { background: #dc2626; transform: translateY(-1px); }
+ }
+}
+.event-card {
+ background: white;
+ border: 1px solid #e2e8f0;
+ padding: 1.5rem;
+ border-radius: 12px;
+ margin-bottom: 1rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ transition: box-shadow 0.2s ease;
+
+ &:hover {
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/app/organizer-dashboard/organizer-dashboard.spec.ts b/frontend/src/app/organizer-dashboard/organizer-dashboard.spec.ts
new file mode 100644
index 0000000..764f5bb
--- /dev/null
+++ b/frontend/src/app/organizer-dashboard/organizer-dashboard.spec.ts
@@ -0,0 +1,22 @@
+// import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+// import { UserDashboardComponent } from './user-dashboard';
+
+// describe('UserDashboard', () => {
+// let component: UserDashboardComponent;
+// let fixture: ComponentFixture;
+
+// beforeEach(async () => {
+// await TestBed.configureTestingModule({
+// imports: [UserDashboardComponent],
+// }).compileComponents();
+
+// fixture = TestBed.createComponent(UserDashboardComponent);
+// component = fixture.componentInstance;
+// await fixture.whenStable();
+// });
+
+// it('should create', () => {
+// expect(component).toBeTruthy();
+// });
+// });
diff --git a/frontend/src/app/organizer-dashboard/organizer-dashboard.ts b/frontend/src/app/organizer-dashboard/organizer-dashboard.ts
new file mode 100644
index 0000000..4e6a78e
--- /dev/null
+++ b/frontend/src/app/organizer-dashboard/organizer-dashboard.ts
@@ -0,0 +1,71 @@
+import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { Router, RouterModule } from '@angular/router';
+import { AuthService } from '../auth/auth.service';
+import { EventService } from '../events/event.service';
+
+@Component({
+ selector: 'app-organizer-dashboard',
+ standalone: true,
+ imports: [CommonModule, RouterModule],
+ templateUrl: './organizer-dashboard.html',
+ styleUrls: ['./organizer-dashboard.scss'],
+})
+export class OrganizerDashboardComponent implements OnInit {
+ user: any = null;
+ myHostedEvents: any[] = [];
+ loading = true;
+
+ constructor(
+ private auth: AuthService,
+ private eventService: EventService,
+ private router: Router,
+ private cdr: ChangeDetectorRef
+ ) {}
+
+ ngOnInit() {
+ this.auth.getProfile().subscribe({
+ next: (data: any) => {
+ this.user = data;
+ if (this.user.role !== 'ORG') {
+ this.router.navigate(['/user-dashboard']);
+ return;
+ }
+ this.loadHostedEvents();
+ },
+ error: () => this.router.navigate(['/login'])
+ });
+ }
+
+ loadHostedEvents() {
+ this.eventService.getOrgEvents().subscribe({
+ next: (data: any[]) => {
+ this.myHostedEvents = data;
+ this.loading = false;
+ this.cdr.detectChanges();
+ },
+ error: (err: any) => {
+ console.error('Error loading events:', err);
+ this.loading = false;
+ this.cdr.detectChanges();
+ }
+ });
+ }
+
+ deleteEvent(eventId: number) {
+ if (confirm('Are you sure you want to delete this event?')) {
+ this.eventService.deleteEvent(eventId).subscribe({
+ next: () => {
+ this.myHostedEvents = this.myHostedEvents.filter(e => e.event_id !== eventId);
+ this.cdr.detectChanges();
+ },
+ error: (err: any) => console.error('Delete failed:', err)
+ });
+ }
+ }
+
+ logout() {
+ this.auth.logout();
+ this.router.navigate(['/login']);
+ }
+}
\ No newline at end of file