Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions src/features/auth/authApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const authApi = createApi({
email: userCredential.user.email!,
name: userCredential.user.displayName!,
role: userData?.role || UserRole.USER,
avatar: userCredential.user.photoURL || undefined, // Added this line
};
return { data: user };
} catch (error) {
Expand Down
157 changes: 124 additions & 33 deletions src/features/booking/bookingApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
query,
where,
orderBy,
Timestamp, // Added Timestamp
} from "firebase/firestore";
import { createApi, fakeBaseQuery } from "@reduxjs/toolkit/query/react";
import { handleError } from "@/helpers/handleError";
Expand All @@ -17,15 +18,27 @@ export interface Booking {
id: string;
eventId: string;
userId: string;
status: "pending" | "confirmed" | "cancelled";
quantity: number;
eventName: string; // Added
eventDate: string; // Added - ISO String
ticketsBooked: number; // Changed from quantity
totalPrice: number;
bookedAt: string;
checkedIn?: boolean;
checkedInAt?: string;
status: "pending" | "confirmed" | "cancelled";
bookedAt: string; // ISO String
checkedIn: boolean; // Not optional, defaults to false
checkedInAt?: string | null; // ISO String or null
paymentDetails: { paymentId: string; status: string }; // Added
}

type CreateBookingDto = Omit<Booking, "id" | "status" | "bookedAt">;
// Fields provided by the client when creating a booking
type CreateBookingDto = {
userId: string;
eventId: string;
eventName: string;
eventDate: string; // Expect ISO string from client
ticketsBooked: number;
totalPrice: number;
paymentDetails: { paymentId: string; status: string };
};

export const bookingApi = createApi({
reducerPath: "bookingApi",
Expand All @@ -41,10 +54,23 @@ export const bookingApi = createApi({
orderBy("bookedAt", "desc")
);
const querySnapshot = await getDocs(q);
const bookings = querySnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
})) as Booking[];
const bookings = querySnapshot.docs.map((doc) => {
const data = doc.data();
return {
id: doc.id,
eventId: data.eventId,
userId: data.userId,
eventName: data.eventName,
eventDate: data.eventDate?.toDate ? data.eventDate.toDate().toISOString() : data.eventDate,
ticketsBooked: data.ticketsBooked,
totalPrice: data.totalPrice,
status: data.status,
bookedAt: data.bookedAt?.toDate ? data.bookedAt.toDate().toISOString() : data.bookedAt,
checkedIn: data.checkedIn || false,
checkedInAt: data.checkedInAt?.toDate ? data.checkedInAt.toDate().toISOString() : data.checkedInAt,
paymentDetails: data.paymentDetails,
} as Booking;
});
return { data: bookings };
} catch (error) {
return handleError(error);
Expand All @@ -71,11 +97,23 @@ export const bookingApi = createApi({
);

const querySnapshot = await getDocs(q);
const bookings = querySnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
})) as Booking[];

const bookings = querySnapshot.docs.map((doc) => {
const data = doc.data();
return {
id: doc.id,
eventId: data.eventId,
userId: data.userId,
eventName: data.eventName,
eventDate: data.eventDate?.toDate ? data.eventDate.toDate().toISOString() : data.eventDate,
ticketsBooked: data.ticketsBooked,
totalPrice: data.totalPrice,
status: data.status,
bookedAt: data.bookedAt?.toDate ? data.bookedAt.toDate().toISOString() : data.bookedAt,
checkedIn: data.checkedIn || false,
checkedInAt: data.checkedInAt?.toDate ? data.checkedInAt.toDate().toISOString() : data.checkedInAt,
paymentDetails: data.paymentDetails,
} as Booking;
});
return { data: bookings };
} catch (indexError: unknown) {
if ((indexError as { code: string }).code === 'failed-precondition') {
Expand All @@ -86,22 +124,37 @@ export const bookingApi = createApi({
);

const querySnapshot = await getDocs(simpleQ);
const bookings = querySnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
})) as Booking[];
let bookings = querySnapshot.docs.map((doc) => {
const data = doc.data();
return {
id: doc.id,
eventId: data.eventId,
userId: data.userId,
eventName: data.eventName,
eventDate: data.eventDate?.toDate ? data.eventDate.toDate().toISOString() : data.eventDate,
ticketsBooked: data.ticketsBooked,
totalPrice: data.totalPrice,
status: data.status,
bookedAt: data.bookedAt?.toDate ? data.bookedAt.toDate().toISOString() : data.bookedAt,
checkedIn: data.checkedIn || false,
checkedInAt: data.checkedInAt?.toDate ? data.checkedInAt.toDate().toISOString() : data.checkedInAt,
paymentDetails: data.paymentDetails,
} as Booking;
});

// Sort manually in memory
bookings.sort((a, b) =>
new Date(b.bookedAt).getTime() - new Date(a.bookedAt).getTime()
);
bookings.sort((a, b) => {
// Ensure bookedAt is a string (ISO) before creating Date objects for sorting
const dateA = typeof a.bookedAt === 'string' ? new Date(a.bookedAt).getTime() : 0;
const dateB = typeof b.bookedAt === 'string' ? new Date(b.bookedAt).getTime() : 0;
return dateB - dateA;
});

return { data: bookings };
}
throw indexError;
}
} catch (error) {
console.error('Error fetching bookings:', error);
return handleError(error);
}
},
Expand All @@ -118,10 +171,23 @@ export const bookingApi = createApi({
orderBy("bookedAt", "desc")
);
const querySnapshot = await getDocs(q);
const bookings = querySnapshot.docs.map((doc) => ({
id: doc.id,
...doc.data(),
})) as Booking[];
const bookings = querySnapshot.docs.map((doc) => {
const data = doc.data();
return {
id: doc.id,
eventId: data.eventId,
userId: data.userId,
eventName: data.eventName,
eventDate: data.eventDate?.toDate ? data.eventDate.toDate().toISOString() : data.eventDate,
ticketsBooked: data.ticketsBooked,
totalPrice: data.totalPrice,
status: data.status,
bookedAt: data.bookedAt?.toDate ? data.bookedAt.toDate().toISOString() : data.bookedAt,
checkedIn: data.checkedIn || false,
checkedInAt: data.checkedInAt?.toDate ? data.checkedInAt.toDate().toISOString() : data.checkedInAt,
paymentDetails: data.paymentDetails,
} as Booking;
});
return { data: bookings };
} catch (error) {
return handleError(error);
Expand All @@ -133,15 +199,40 @@ export const bookingApi = createApi({
createBooking: builder.mutation<Booking, CreateBookingDto>({
async queryFn(bookingData) {
try {
const booking = {
...bookingData,
// Data to be stored in Firestore, converting dates to Timestamps
const dataToStore = {
userId: bookingData.userId,
eventId: bookingData.eventId,
eventName: bookingData.eventName,
eventDate: Timestamp.fromDate(new Date(bookingData.eventDate)), // Convert ISO string to Timestamp
ticketsBooked: bookingData.ticketsBooked,
totalPrice: bookingData.totalPrice,
paymentDetails: bookingData.paymentDetails,
status: "confirmed" as const,
bookedAt: new Date().toISOString(),
bookedAt: Timestamp.fromDate(new Date()), // Store as Timestamp
checkedIn: false,
checkedInAt: null, // Initialize checkedInAt as null
};

const bookingsRef = collection(db, "bookings");
const docRef = await addDoc(bookingsRef, booking);
return { data: { id: docRef.id, ...booking } };
const docRef = await addDoc(bookingsRef, dataToStore);

// Data to return to the client, conforming to the Booking interface (dates as ISO strings)
const newBooking: Booking = {
id: docRef.id,
userId: bookingData.userId,
eventId: bookingData.eventId,
eventName: bookingData.eventName,
eventDate: bookingData.eventDate, // Return the original ISO string for eventDate
ticketsBooked: bookingData.ticketsBooked,
totalPrice: bookingData.totalPrice,
paymentDetails: bookingData.paymentDetails,
status: "confirmed",
bookedAt: dataToStore.bookedAt.toDate().toISOString(), // Convert stored Timestamp back to ISO string
checkedIn: false,
checkedInAt: null,
};
return { data: newBooking };
} catch (error) {
return handleError(error);
}
Expand Down Expand Up @@ -171,7 +262,7 @@ export const bookingApi = createApi({
const docRef = doc(db, "bookings", bookingId);
await updateDoc(docRef, {
checkedIn: true,
checkedInAt: new Date().toISOString(),
checkedInAt: Timestamp.fromDate(new Date()), // Store as Timestamp
});
return { data: undefined };
} catch (error) {
Expand Down
14 changes: 0 additions & 14 deletions src/features/events/eventApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ export const eventApi = createApi({
getEvents: builder.query<Event[], EventFilters | void>({
async queryFn(filters: EventFilters = {}) {
try {
console.log("Applying filters:", JSON.stringify(filters, null, 2));

const eventsRef = collection(db, "events");
const queryConstraints = [];

Expand All @@ -43,8 +41,6 @@ export const eventApi = createApi({


if (filters.tags && filters.tags.length > 0) {
console.log(`Filtering by tags: ${filters.tags.join(', ')}`);

if (filters.tags.length === 1) {

queryConstraints.push(where("tags", "array-contains", filters.tags[0]));
Expand Down Expand Up @@ -78,12 +74,8 @@ export const eventApi = createApi({
? query(eventsRef, ...queryConstraints)
: query(eventsRef, orderBy("date", "asc"));

console.log("Executing query with constraints:", queryConstraints.length);

try {
const querySnapshot = await getDocs(baseQuery);
console.log(`Found ${querySnapshot.docs.length} events from database`);

let events = querySnapshot.docs.map((doc) => {
const data = doc.data();

Expand All @@ -105,15 +97,13 @@ export const eventApi = createApi({

// Handle category filtering client-side if we couldn't use it in the query
if (filters.category && hasOtherFilters) {
console.log(`Filtering by category client-side: ${filters.category}`);
events = events.filter(event =>
event.category?.toLowerCase() === filters.category?.toLowerCase()
);
}

// Handle additional tag filtering client-side if needed (for more than 10 tags)
if (filters.tags && filters.tags.length > 10) {
console.log("Filtering additional tags client-side");
events = events.filter((event) => {
// For tags beyond the 10th, check if they exist in the event tags
return filters.tags!.slice(10).every(tag =>
Expand All @@ -127,7 +117,6 @@ export const eventApi = createApi({
// Apply search filter client-side
if (filters.search) {
const term = filters.search.toLowerCase();
console.log(`Applying search filter: ${term}`);
events = events.filter((event) =>
event.title.toLowerCase().includes(term) ||
event.description.toLowerCase().includes(term) ||
Expand All @@ -140,14 +129,11 @@ export const eventApi = createApi({
// Ensure events are sorted by date
events.sort((a, b) => new Date(a.date).getTime() - new Date(b.date).getTime());

console.log(`Returning ${events.length} events after all filters`);
return { data: events };
} catch (queryError) {
console.error('Error executing query:', queryError);
return handleError(queryError);
}
} catch (error) {
console.error('Error in getEvents:', error);
return handleError(error);
}
},
Expand Down
6 changes: 6 additions & 0 deletions src/mocks/browser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';

// This configures a Service Worker request handler with the given request handlers.
export const worker = setupWorker(...handlers);
17 changes: 17 additions & 0 deletions src/mocks/handlers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw';

// Define handlers for Firebase/Firestore authentication endpoints
export const handlers = [
// Example handler for createUserWithEmailAndPassword
http.post('https://identitytoolkit.googleapis.com/v1/accounts:signUp', () => {
return HttpResponse.json({
idToken: 'fake-id-token',
email: 'user@example.com',
refreshToken: 'fake-refresh-token',
expiresIn: '3600',
localId: 'fake-local-id',
});
}),
// Add other handlers here as needed for signInWithEmailAndPassword, etc.
];
6 changes: 6 additions & 0 deletions src/mocks/server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// src/mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';

// This configures a request mocking server with the given request handlers.
export const server = setupServer(...handlers);
13 changes: 13 additions & 0 deletions src/setupTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// src/setupTests.ts
import { server } from './mocks/server';
import '@testing-library/jest-dom';

// Establish API mocking before all tests.
beforeAll(() => server.listen());

// Reset any request handlers that we may add during the tests,
// so they don't affect other tests.
afterEach(() => server.resetHandlers());

// Clean up after the tests are finished.
afterAll(() => server.close());
Loading