Skip to content
Merged
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
48 changes: 30 additions & 18 deletions apps/web/src/hooks/useBookingDetails.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,37 +29,49 @@ export function useBookingDetails(bookingId: string): UseBookingDetailsReturn {
setError(null);

// Validate booking ID
if (!bookingId || bookingId.length < 3) {
if (!bookingId || !isValidBookingId(bookingId)) {
throw new Error('Invalid booking ID');
}

await new Promise((resolve) => setTimeout(resolve, 1000));
// Generate more realistic and dynamic mock data
const mockData: BookingData = {
id: bookingId,
const response = await fetch(`/api/bookings/${bookingId}`);

if (!response.ok) {
if (response.status === 404) {
throw new Error('Booking not found');
}
throw new Error(`Failed to fetch booking: ${response.statusText}`);
}

const data = await response.json();

// Ensure the response matches our expected BookingData type
const booking: BookingData = {
id: data.id || bookingId,
property: {
title: `Property ${bookingId.slice(-3)}`,
image: '/images/property-placeholder.jpg',
title: data.property?.title || 'Unknown Property',
image: data.property?.image || '/images/property-placeholder.jpg',
},
dates: {
from: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // 1 week from now
to: new Date(Date.now() + 12 * 24 * 60 * 60 * 1000), // 12 days from now
from: data.dates?.from ? new Date(data.dates.from) : new Date(),
to: data.dates?.to ? new Date(data.dates.to) : new Date(),
},
guests: 2,
totalAmount: 750,
transactionHash: `0x${bookingId.toLowerCase().padEnd(32, '0')}`,
escrowStatus: 'pending',
guests: data.guests || 1,
totalAmount: data.totalAmount || 0,
transactionHash: data.transactionHash || `0x${bookingId.toLowerCase().padEnd(32, '0')}`,
escrowStatus: data.escrowStatus || 'pending',
host: {
name: `Host ${bookingId.slice(-2)}`,
email: `host-${bookingId.toLowerCase()}@example.com`,
phone: '+1 (555) 123-4567',
name: data.host?.name || 'Host',
email: data.host?.email || '',
phone: data.host?.phone || '',
},
};

setBookingData(mockData);
setBookingData(booking);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load booking data');
const errorMessage = err instanceof Error ? err.message : 'Failed to load booking data';
setError(errorMessage);
setBookingData(null);
console.error('Error fetching booking data:', err);
} finally {
Comment on lines +36 to 75
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | πŸ”΄ Critical

Restore auth-aware booking request

Switching to fetch('/api/bookings/...') bypasses the API base URL and silently drops the bearer token that apiUtils.request used to attach. Any protected booking lookup will now 401/404 in production. Please reuse the API helper logic (or reintroduce the same headers/credentials inline) before parsing the response.

-      const response = await fetch(`/api/bookings/${bookingId}`);
+      const authToken = typeof window !== 'undefined' ? localStorage.getItem('authToken') : null;
+      const baseUrl = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3001/api';
+      const response = await fetch(`${baseUrl}/bookings/${bookingId}`, {
+        headers: {
+          'Content-Type': 'application/json',
+          ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
+        },
+        credentials: 'include',
+      });

<!-- suggestion_start -->

<details>
<summary>πŸ“ Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
      // Restore API base URL and auth headers
      const authToken = typeof window !== 'undefined' ? localStorage.getItem('authToken') : null;
      const baseUrl = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:3001/api';
      const response = await fetch(`${baseUrl}/bookings/${bookingId}`, {
        headers: {
          'Content-Type': 'application/json',
          ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}),
        },
        credentials: 'include',
      });

      if (!response.ok) {
        if (response.status === 404) {
          throw new Error('Booking not found');
        }
        throw new Error(`Failed to fetch booking: ${response.statusText}`);
      }

      const data = await response.json();
      
      // Ensure the response matches our expected BookingData type
      const booking: BookingData = {
        id: data.id || bookingId,
        property: {
          title: data.property?.title || 'Unknown Property',
          image: data.property?.image || '/images/property-placeholder.jpg',
        },
        dates: {
          from: data.dates?.from ? new Date(data.dates.from) : new Date(),
          to: data.dates?.to ? new Date(data.dates.to) : new Date(),
        },
        guests: data.guests || 1,
        totalAmount: data.totalAmount || 0,
        transactionHash: data.transactionHash || `0x${bookingId.toLowerCase().padEnd(32, '0')}`,
        escrowStatus: data.escrowStatus || 'pending',
        host: {
          name: data.host?.name || 'Host',
          email: data.host?.email || '',
          phone: data.host?.phone || '',
        },
      };

      setBookingData(booking);
    } catch (err) {
      const errorMessage = err instanceof Error ? err.message : 'Failed to load booking data';
      setError(errorMessage);
      setBookingData(null);
      console.error('Error fetching booking data:', err);
    } finally {
πŸ€– Prompt for AI Agents
In apps/web/src/hooks/useBookingDetails.ts around lines 36 to 75, the fetch call
was changed to a bare fetch(`/api/bookings/${bookingId}`) which bypasses the
app's API base URL and drops authentication headers/cookies; restore auth-aware
requests by using the existing api helper or adding the same
headers/credentials: construct the full API URL (use NEXT_PUBLIC_API_URL
fallback), read the auth token from localStorage if in the browser, include
Authorization: Bearer <token> when present, set Content-Type to application/json
and credentials:'include' (or reuse apiUtils.request which already does this),
then replace the current fetch call with that authenticated request before
parsing the response.

setLoading(false);
}
Expand Down
120 changes: 106 additions & 14 deletions apps/web/src/hooks/useDashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,16 +56,23 @@ export const useDashboard = ({ userId, userType }: UseDashboardProps): UseDashbo
setError(null);

try {
const response = await bookingAPI.getBookings(userId, { userType });
setBookings(response.data || []);
const response = await fetch('/api/bookings');
if (!response.ok) {
throw new Error(`Failed to fetch bookings: ${response.statusText}`);
}
const data = await response.json();
// Handle the response structure from backend
const bookingsData = data.data?.bookings || data.bookings || [];
setBookings(Array.isArray(bookingsData) ? bookingsData : []);
} catch (err) {
const errorMessage = handleAPIError(err);
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch bookings';
setError(errorMessage);
console.error('Failed to fetch bookings:', err);
setBookings([]); // Reset to empty array on error
} finally {
setIsLoadingBookings(false);
}
}, [userId, userType]);
}, [userId]);

const fetchProfile = useCallback(async () => {
if (!userId) return;
Expand All @@ -74,12 +81,17 @@ export const useDashboard = ({ userId, userType }: UseDashboardProps): UseDashbo
setError(null);

try {
const response = await profileAPI.getUserProfile(userId);
setProfile(response.data);
const response = await fetch('/api/profile');
if (!response.ok) {
throw new Error(`Failed to fetch profile: ${response.statusText}`);
}
const data = await response.json();
setProfile(data || null);
} catch (err) {
const errorMessage = handleAPIError(err);
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch profile';
setError(errorMessage);
console.error('Failed to fetch profile:', err);
setProfile(null); // Reset to null on error
} finally {
setIsLoadingProfile(false);
}
Expand All @@ -92,12 +104,51 @@ export const useDashboard = ({ userId, userType }: UseDashboardProps): UseDashbo
setError(null);

try {
const response = await walletAPI.getTransactionHistory(userId);
setTransactions(response.data || []);
// Use mock data until wallet transactions endpoint is implemented
// TODO: Replace with real API call when /api/wallet/transactions is available
const mockTransactions: Transaction[] = [
{
id: '1',
date: '2025-05-28',
description: 'Luxury Downtown Apartment',
amount: -1250,
type: 'booking',
status: 'completed',
},
{
id: '2',
date: '2025-05-26',
description: 'Cozy Beach House',
amount: -900,
type: 'booking',
status: 'pending',
},
{
id: '3',
date: '2025-05-20',
description: 'Wallet Top-up',
amount: 2000,
type: 'deposit',
status: 'completed',
},
{
id: '4',
date: '2025-05-15',
description: 'Mountain Cabin Retreat',
amount: -1600,
type: 'booking',
status: 'completed',
},
];

// Simulate API delay
await new Promise((resolve) => setTimeout(resolve, 500));
setTransactions(mockTransactions);
} catch (err) {
const errorMessage = handleAPIError(err);
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch transactions';
setError(errorMessage);
console.error('Failed to fetch transactions:', err);
setTransactions([]); // Reset to empty array on error
} finally {
setIsLoadingTransactions(false);
}
Expand All @@ -110,12 +161,53 @@ export const useDashboard = ({ userId, userType }: UseDashboardProps): UseDashbo
setError(null);

try {
const response = await dashboardAPI.getDashboardStats(userId, userType);
setStats(response.data);
// Calculate stats from bookings data instead of calling non-existent analytics endpoint
// TODO: Replace with real analytics API when /api/analytics/overview is implemented
const response = await fetch('/api/bookings');
if (!response.ok) {
throw new Error(`Failed to fetch bookings for stats: ${response.statusText}`);
}
const data = await response.json();
const bookingsData = data.data?.bookings || data.bookings || [];

// Calculate stats from bookings
const totalBookings = bookingsData.length;
const totalEarnings = bookingsData.reduce(
(sum: number, booking: { total: string | number }) => {
return sum + (Number.parseFloat(String(booking.total)) || 0);
},
0
);
const pendingBookings = bookingsData.filter(
(booking: { status: string }) => booking.status === 'pending'
).length;
const completedBookings = bookingsData.filter(
(booking: { status: string }) => booking.status === 'completed'
).length;

const calculatedStats: DashboardStats = {
totalBookings,
totalEarnings,
averageRating: 4.8, // Mock rating until reviews are implemented
activeProperties: userType === 'host' ? totalBookings : 0, // Mock for now
pendingBookings,
completedBookings,
};

setStats(calculatedStats);
} catch (err) {
const errorMessage = handleAPIError(err);
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch dashboard stats';
setError(errorMessage);
console.error('Failed to fetch dashboard stats:', err);
// Set default stats on error
setStats({
totalBookings: 0,
totalEarnings: 0,
averageRating: 0,
activeProperties: 0,
pendingBookings: 0,
completedBookings: 0,
});
} finally {
setIsLoadingStats(false);
}
Expand All @@ -134,7 +226,7 @@ export const useDashboard = ({ userId, userType }: UseDashboardProps): UseDashbo
if (userId) {
refreshAll();
}
}, [userId, userType, refreshAll]);
}, [userId, refreshAll]);

return {
bookings,
Expand Down
Loading