Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
567f4ee
EMS-140: Fix notification service (APP_NAME)
ashwin-athappan Nov 14, 2025
7a8b788
EMS-140: test(auth-service): Add comprehensive tests for seeder.route…
ashwin-athappan Nov 14, 2025
5e7c2f9
EMS-140: test(booking-service): Add new test files to significantly i…
ashwin-athappan Nov 14, 2025
6b8924b
EMS-140: feat(admin): Add client-compatible routes for event tickets
ashwin-athappan Nov 15, 2025
7578da5
EMS-140: feat(profile): Create profile page for information and passw…
ashwin-athappan Nov 15, 2025
ac2264e
EMS-140: refactor(attendee): Modularize dashboard and fix schedule bugs
ashwin-athappan Nov 15, 2025
7734fa4
EMS-140: feat(dashboard): Add ThemeToggle via shared DashboardHeader …
ashwin-athappan Nov 15, 2025
79b74b5
EMS-79: Test suites for all backend services improved
ashwin-athappan Nov 15, 2025
5d908e6
EMS-140: feat: Replace datetime-local inputs with DateTimeSelector an…
ashwin-athappan Nov 17, 2025
431dd6a
EMS-140: fix: Enhance loadAllSpeakers to include event-level and join…
ashwin-athappan Nov 17, 2025
87c2be0
EMS-140: refactor: Conditionally render DashboardHeader only on root …
ashwin-athappan Nov 17, 2025
3078cf5
fix: selenium build issue in client microservice
Buffden Nov 17, 2025
d257631
EMS-140: feat(admin): Add create venue page and dashboard link
ashwin-athappan Nov 18, 2025
28c9e29
EMS-140: feat(admin): Add "Back to Dashboard" button to ticket manage…
ashwin-athappan Nov 18, 2025
05e9590
EMS-140: feat(notification-service): Add consumer for event update em…
ashwin-athappan Nov 18, 2025
badb9f2
EMS-140: refactor(auth-service): Reuse /admin/users endpoint for inte…
ashwin-athappan Nov 18, 2025
8f023bd
EMS-140: feat(notification-service): Update new event email link to p…
ashwin-athappan Nov 18, 2025
133a4a3
EMS-140: fix(client): Fix auth redirect loop on dashboard page refresh
ashwin-athappan Nov 18, 2025
8296180
EMS-140: (test): Fix failing test cases
ashwin-athappan Nov 18, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
378 changes: 77 additions & 301 deletions ems-client/app/dashboard/admin/events/create/page.tsx

Large diffs are not rendered by default.

48 changes: 30 additions & 18 deletions ems-client/app/dashboard/admin/events/modify/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ function AdminModifyEventPage() {
const [eventInvitations, setEventInvitations] = useState<SpeakerInvitation[]>([]);
const [loadingInvitations, setLoadingInvitations] = useState(false);
const [acceptedSpeakerFromInvitation, setAcceptedSpeakerFromInvitation] = useState<SpeakerProfile | null>(null);

// Sessions and accepted speakers state
const [sessions, setSessions] = useState<SessionResponse[]>([]);
const [loadingSessions, setLoadingSessions] = useState(false);
Expand Down Expand Up @@ -241,7 +241,7 @@ function AdminModifyEventPage() {
// Create invitation lookup map by sessionId and speakerId
const invitationMap = new Map<string, SpeakerInvitation>();
eventInvitations.forEach(invitation => {
const key = invitation.sessionId
const key = invitation.sessionId
? `${invitation.sessionId}:${invitation.speakerId}`
: `event-level:${invitation.speakerId}`;
invitationMap.set(key, invitation);
Expand All @@ -250,12 +250,12 @@ function AdminModifyEventPage() {
// Create a map to track all session-speaker combinations
// Key: `${sessionId}:${speakerId}` or `event-level:${speakerId}`
const sessionSpeakerMap = new Map<string, { speakerId: string; session?: SessionResponse; invitation?: SpeakerInvitation }>();

// Process all session speakers
allSessionSpeakers.forEach(({ speakerId, session }) => {
const key = session ? `${session.id}:${speakerId}` : `event-level:${speakerId}`;
const invitation = invitationMap.get(key);

// Store each session-speaker combination separately
sessionSpeakerMap.set(key, { speakerId, session, invitation });
});
Expand All @@ -265,7 +265,7 @@ function AdminModifyEventPage() {
Array.from(sessionSpeakerMap.values()).map(async ({ speakerId, session, invitation }) => {
try {
const speaker = await adminApiClient.getSpeakerProfile(speakerId);

return {
invitation: invitation || null,
speaker,
Expand All @@ -283,7 +283,7 @@ function AdminModifyEventPage() {

// Filter out any null results
const validSpeakers = speakersWithInfo.filter((item): item is NonNullable<typeof item> => item !== null);

setAcceptedSpeakers(validSpeakers);

logger.info(LOGGER_COMPONENT_NAME, 'Speakers loaded for sessions', {
Expand Down Expand Up @@ -767,16 +767,27 @@ function AdminModifyEventPage() {
{/* Speaker Assignment */}
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="text-lg font-semibold text-slate-900 dark:text-white flex items-center">
<Users className="h-5 w-5 mr-2" />
Speaker Assignment
</h3>
{acceptedSpeakers.length > 0 && (
<Badge variant="outline" className="ml-2">
{acceptedSpeakers.length} accepted speaker{acceptedSpeakers.length !== 1 ? 's' : ''}
</Badge>
)}
</div>
<div className="flex items-center gap-2">
<h3 className="text-lg font-semibold text-slate-900 dark:text-white flex items-center">
<Users className="h-5 w-5 mr-2" />
Speaker Assignment
</h3>
{acceptedSpeakers.length > 0 && (
<Badge variant="outline" className="ml-2">
{acceptedSpeakers.length} accepted speaker{acceptedSpeakers.length !== 1 ? 's' : ''}
</Badge>
)}
</div>
<Button
type="button"
size="sm"
onClick={() => setShowSpeakerSearchModal(true)}
className="bg-blue-600 hover:bg-blue-700"
>
<UserPlus className="h-4 w-4 mr-2" />
Invite Speaker
</Button>
</div>

{/* Accepted Speakers List */}
{loadingAcceptedSpeakers ? (
Expand Down Expand Up @@ -846,8 +857,8 @@ function AdminModifyEventPage() {
}
};

const textColorClass = invitation && invitation.status === 'ACCEPTED'
? "text-green-800 dark:text-green-200"
const textColorClass = invitation && invitation.status === 'ACCEPTED'
? "text-green-800 dark:text-green-200"
: invitation && invitation.status === 'PENDING'
? "text-yellow-800 dark:text-yellow-200"
: invitation && invitation.status === 'DECLINED'
Expand Down Expand Up @@ -1181,6 +1192,7 @@ function AdminModifyEventPage() {
eventId={eventId}
eventName={originalEvent?.name || 'Event'}
currentSpeakerId={originalEvent?.speakerId}
invitedSpeakerIds={eventInvitations.map(inv => inv.speakerId)}
/>
</div>
);
Expand Down
37 changes: 37 additions & 0 deletions ems-client/app/dashboard/admin/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use client';

import React from 'react';
import { useAuth } from '@/lib/auth-context';
import { DashboardHeader } from '@/components/dashboard/DashboardHeader';
import { usePathname } from 'next/navigation';

export default function AdminLayout({
children,
}: {
children: React.ReactNode;
}) {
const { user, logout } = useAuth();
const pathname = usePathname();

// Only show layout on root dashboard page (/dashboard/admin)
const isRootDashboard = pathname === '/dashboard/admin';

return (
<>
{isRootDashboard && (
<DashboardHeader
user={user}
onLogout={logout}
title="EventManager"
badge={{
label: 'Admin Panel',
variant: 'secondary',
className: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200'
}}
/>
)}
{children}
</>
);
}

12 changes: 7 additions & 5 deletions ems-client/app/dashboard/admin/messages/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -218,16 +218,18 @@ function AdminMessagingCenter() {

// Load all users
const usersResponse = await adminApiClient.getAllUsers({ limit: 1000 });
const users = usersResponse.data.map(user => ({
const users = (usersResponse.data || []).map(user => ({
id: user.id,
name: user.name || user.email,
email: user.email,
type: 'user' as const
}));

// Load all speakers
const speakers = await speakerApiClient.searchSpeakers({ query: '', limit: 1000 });
const speakersList = speakers.map(speaker => ({
// Load all speakers - use undefined for query to get all speakers
const speakers = await speakerApiClient.searchSpeakers({ limit: 1000 });
// Ensure speakers is an array
const speakersArray = Array.isArray(speakers) ? speakers : [];
const speakersList = speakersArray.map(speaker => ({
id: speaker.userId,
name: speaker.name,
email: speaker.email,
Expand All @@ -245,7 +247,7 @@ function AdminMessagingCenter() {

logger.info(LOGGER_COMPONENT_NAME, 'All recipients loaded', {
usersCount: users.length,
speakersCount: speakers.length,
speakersCount: speakersArray.length,
totalCount: allRecipientsList.length
});
} catch (error) {
Expand Down
59 changes: 11 additions & 48 deletions ems-client/app/dashboard/admin/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,16 @@
import { useAuth } from "@/lib/auth-context";
import { Button } from "@/components/ui/button";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import { Badge } from "@/components/ui/badge";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import {
LogOut,
Users,
Calendar,
UserCheck,
BarChart3,
Plus,
Eye,
Ticket,
MessageSquare
MessageSquare,
MapPin
} from "lucide-react";
import { useRouter } from "next/navigation";
import { useEffect, useState } from "react";
Expand Down Expand Up @@ -57,50 +55,6 @@ function AdminDashboard() {

return (
<div className="min-h-screen bg-gradient-to-br from-blue-50 via-white to-indigo-50 dark:from-slate-900 dark:via-slate-800 dark:to-slate-900">
{/* Header */}
<header className="bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm border-b border-slate-200 dark:border-slate-700">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between items-center h-16">
<div className="flex items-center space-x-4">
<h1 className="text-2xl font-bold bg-gradient-to-r from-blue-600 to-indigo-600 bg-clip-text text-transparent">
EventManager
</h1>
<Badge variant="secondary" className="bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200">
Admin Panel
</Badge>
</div>

<div className="flex items-center space-x-4">
<div className="flex items-center space-x-3">
<Avatar className="h-8 w-8">
<AvatarImage
src={user?.image || `https://api.dicebear.com/7.x/initials/svg?seed=${user?.name || user?.email}`}
alt={user?.name || user?.email}
/>
<AvatarFallback className="text-xs">
{user?.name ? user.name.split(' ').map(n => n[0]).join('') : user?.email?.[0]?.toUpperCase()}
</AvatarFallback>
</Avatar>
<span className="text-sm font-medium text-slate-700 dark:text-slate-300">
{user?.name}
</span>
</div>

<Button
variant="outline"
size="sm"
onClick={logout}
className="text-slate-600 hover:text-red-600 dark:text-slate-400 dark:hover:text-red-400"
>
<LogOut className="h-4 w-4 mr-2" />
Sign Out
</Button>
</div>
</div>
</div>
</header>


{/* Main Content */}
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Welcome Section */}
Expand Down Expand Up @@ -257,6 +211,15 @@ function AdminDashboard() {
<MessageSquare className="h-5 w-5" />
<span className="text-sm">Messages</span>
</Button>

<Button
variant="outline"
className="h-20 flex flex-col items-center justify-center space-y-2 border-slate-200 dark:border-slate-700"
onClick={() => router.push('/dashboard/admin/venues/create')}
>
<MapPin className="h-5 w-5" />
<span className="text-sm">Add New Venue</span>
</Button>
</div>
</CardContent>
</Card>
Expand Down
Loading