fix-141-dashboard-hooks-call-non-existent-api-endpoints#177
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughReplaces mocked data and thin service wrappers with fetch-based API calls across useBookingDetails, useDashboard, and services/api. Adds booking ID validation, explicit HTTP-status checks, centralized _apiCall for headers/auth/error handling, per-endpoint try/catch with defensive defaults, and updated wallet/dashboard/notification method shapes. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor User
participant Hook as useBookingDetails
participant API as /api/bookings/{id}
note over Hook: Validate bookingId first
User->>Hook: useBookingDetails(bookingId)
alt invalid id
Hook-->>User: setError("Invalid booking ID"), data=null
else valid id
Hook->>API: GET /api/bookings/{id}
alt 200
API-->>Hook: 200 + JSON
Hook-->>User: setData(mapped BookingData)
else 404
API-->>Hook: 404
Hook-->>User: setError("Booking not found"), data=null
else error
API-->>Hook: non-OK
Hook-->>User: setError(generic), data=null
end
end
note over Hook: loading=false
sequenceDiagram
autonumber
actor User
participant Hook as useDashboard
participant B as /api/bookings
participant P as /api/profile
participant T as /api/transactions
participant S as /api/dashboard/stats
User->>Hook: refreshAll()
par Fetch bookings
Hook->>B: GET
alt ok
B-->>Hook: bookings[]
Hook-->>User: setBookings(list)
else error
B-->>Hook: error
Hook-->>User: setBookings([])
end
and Fetch profile
Hook->>P: GET
alt ok
P-->>Hook: profile
Hook-->>User: setProfile(obj)
else error
P-->>Hook: error
Hook-->>User: setProfile(null)
end
and Fetch transactions
Hook->>T: GET (mock until backend ready)
alt ok
T-->>Hook: txns[]
Hook-->>User: setTransactions(list)
else error
T-->>Hook: error
Hook-->>User: setTransactions([])
end
and Compute/fetch stats
Hook->>S: GET or compute from bookings
alt ok
S-->>Hook: stats
Hook-->>User: setStats(obj)
else error
S-->>Hook: error
Hook-->>User: setStats(defaultZeros)
end
end
note over Hook: per-section loading flags updated
sequenceDiagram
autonumber
participant Caller as services/* method
participant Core as _apiCall
participant Backend as HTTP endpoint
Caller->>Core: _apiCall(url, opts {method, headers, body, token})
Core->>Backend: fetch(url, {method, headers + Authorization?, credentials: "include", body})
alt 204 No Content
Backend-->>Core: 204
Core-->>Caller: { ok: true, data: null }
else 2xx
Backend-->>Core: 200 + JSON
Core-->>Caller: parsed data
else non-OK
Backend-->>Core: status != 2xx + error body
Core-->>Caller: throw Error(extractedMessage)
end
note over Caller: callers wrap calls in try/catch and return defaults on failure
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/web/src/hooks/useDashboard.ts (1)
58-146: Keep dashboard fetches behind the authenticated clientAll four new
fetch('/api/...')calls now omit the Authorization header and API base handling thatbookingAPI/profileAPI/walletAPI/dashboardAPI(viaapiUtils.request) previously supplied. Those endpoints are protected, so these calls will start failing with 401s once cookies aren’t enough. Please route through the shared API helper again (or at least reintroduce the base URL + bearer token) for bookings, profile, transactions, and stats.- const response = await fetch(`/api/bookings?userId=${userId}&userType=${userType}`); + 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?userId=${encodeURIComponent(userId)}&userType=${encodeURIComponent(userType)}`, { + headers: { + 'Content-Type': 'application/json', + ...(authToken ? { Authorization: `Bearer ${authToken}` } : {}), + }, + credentials: 'include', + });(The same adjustment is needed for
fetchProfile,fetchTransactions, andfetchStats.)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
apps/web/src/hooks/useBookingDetails.ts(1 hunks)apps/web/src/hooks/useDashboard.ts(4 hunks)apps/web/src/services/api.ts(2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/src/hooks/useBookingDetails.ts (1)
apps/web/src/types/booking.ts (1)
BookingData(3-22)
| 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 { |
There was a problem hiding this comment.
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.
| getWalletBalance: async (userId: string) => { | ||
| try { | ||
| const response = await fetch(`/api/wallets/${userId}/balance`); | ||
| if (!response.ok) { | ||
| throw new Error(`HTTP error! status: ${response.status}`); | ||
| } | ||
| const data = await response.json(); | ||
| return data; | ||
| } catch (error) { | ||
| console.error('Failed to fetch wallet balance:', error); | ||
| return { | ||
| success: false, | ||
| data: { balance: 0, currency: 'USD' }, | ||
| message: 'Failed to load wallet balance' | ||
| }; | ||
| } | ||
| }, | ||
|
|
||
| async getTransactionHistory(userId: string, filters?: Record<string, unknown>) { | ||
| const params = new URLSearchParams({ userId, ...filters }); | ||
| return apiUtils.request(`/wallet/${userId}/transactions?${params}`); | ||
|
|
||
| getTransactionHistory: async (userId: string, filters?: Record<string, unknown>) => { | ||
| try { | ||
| const params = new URLSearchParams({ userId }); | ||
| if (filters) { | ||
| Object.entries(filters).forEach(([key, value]) => { | ||
| if (value) params.append(key, String(value)); | ||
| }); | ||
| } | ||
| const response = await fetch(`/api/wallets/${userId}/transactions?${params}`); | ||
| if (!response.ok) { | ||
| throw new Error(`HTTP error! status: ${response.status}`); | ||
| } | ||
| const data = await response.json(); | ||
| return data; | ||
| } catch (error) { | ||
| console.error('Failed to fetch transaction history:', error); | ||
| return { | ||
| success: false, | ||
| data: [], | ||
| message: 'Failed to load transaction history' | ||
| }; | ||
| } | ||
| }, | ||
|
|
||
| async addFunds(userId: string, amount: number, paymentMethod: string) { | ||
| return apiUtils.request(`/wallet/${userId}/add-funds`, { | ||
| method: 'POST', | ||
| body: JSON.stringify({ amount, paymentMethod }), | ||
| }); | ||
|
|
||
| addFunds: async (userId: string, amount: number, paymentMethod: string) => { | ||
| try { | ||
| const response = await fetch(`/api/wallets/${userId}/deposit`, { | ||
| method: 'POST', | ||
| body: JSON.stringify({ amount, paymentMethod }), | ||
| }); | ||
| if (!response.ok) { | ||
| throw new Error(`HTTP error! status: ${response.status}`); | ||
| } | ||
| const data = await response.json(); | ||
| return data; | ||
| } catch (error) { | ||
| console.error('Failed to add funds:', error); | ||
| throw new Error('Failed to process deposit'); | ||
| } | ||
| }, | ||
|
|
||
| async withdrawFunds(userId: string, amount: number, accountDetails: Record<string, unknown>) { | ||
| return apiUtils.request(`/wallet/${userId}/withdraw`, { | ||
| method: 'POST', | ||
| body: JSON.stringify({ amount, accountDetails }), | ||
| }); | ||
|
|
||
| withdrawFunds: async (userId: string, amount: number, accountDetails: Record<string, unknown>) => { | ||
| try { | ||
| const response = await fetch(`/api/wallets/${userId}/withdraw`, { | ||
| method: 'POST', | ||
| body: JSON.stringify({ amount, accountDetails }), | ||
| }); | ||
| if (!response.ok) { | ||
| throw new Error(`HTTP error! status: ${response.status}`); | ||
| } | ||
| const data = await response.json(); | ||
| return data; | ||
| } catch (error) { | ||
| console.error('Failed to withdraw funds:', error); | ||
| throw new Error('Failed to process withdrawal'); | ||
| } | ||
| }, | ||
| }; | ||
|
|
||
| export const notificationAPI = { | ||
| async getNotifications(userId: string, filters?: Record<string, unknown>) { | ||
| const params = new URLSearchParams({ userId, ...filters }); | ||
| return apiUtils.request(`/notifications?${params}`); | ||
| }, | ||
|
|
||
| async markAsRead(notificationId: string) { | ||
| return apiUtils.request(`/notifications/${notificationId}/read`, { | ||
| method: 'PUT', | ||
| }); | ||
| }, | ||
|
|
||
| async markAllAsRead(userId: string) { | ||
| return apiUtils.request(`/notifications/${userId}/read-all`, { | ||
| method: 'PUT', | ||
| }); | ||
| }, | ||
|
|
||
| async deleteNotification(notificationId: string) { | ||
| return apiUtils.request(`/notifications/${notificationId}`, { | ||
| method: 'DELETE', | ||
| }); | ||
| }, | ||
|
|
||
| async deleteAllNotifications(userId: string) { | ||
| return apiUtils.request(`/notifications/${userId}/delete-all`, { | ||
| method: 'DELETE', | ||
| {{ ... }} | ||
| }); | ||
| }, | ||
| }; | ||
|
|
||
| export const dashboardAPI = { | ||
| async getDashboardStats(userId: string, userType: 'host' | 'tenant') { | ||
| return apiUtils.request(`/dashboard/${userType}/${userId}/stats`); | ||
| getDashboardStats: async (userId: string, userType: 'host' | 'tenant') => { | ||
| try { | ||
| const response = await fetch(`/api/analytics/overview?userId=${userId}&userType=${userType}`); | ||
| if (!response.ok) { | ||
| throw new Error(`HTTP error! status: ${response.status}`); | ||
| } | ||
| const data = await response.json(); | ||
| return data; | ||
| } catch (error) { | ||
| console.error('Failed to fetch dashboard stats:', error); | ||
| return { | ||
| success: false, | ||
| data: { | ||
| totalBookings: 0, | ||
| totalEarnings: 0, | ||
| activeListings: 0, | ||
| pendingRequests: 0 | ||
| }, | ||
| message: 'Failed to load dashboard stats' | ||
| }; | ||
| } | ||
| }, | ||
|
|
||
| async getRecentActivity(userId: string, userType: 'host' | 'tenant') { | ||
| return apiUtils.request(`/dashboard/${userType}/${userId}/activity`); | ||
|
|
||
| getRecentActivity: async (userId: string, userType: 'host' | 'tenant') => { | ||
| try { | ||
| const response = await fetch(`/api/activity/recent?userId=${userId}&userType=${userType}`); | ||
| if (!response.ok) { | ||
| throw new Error(`HTTP error! status: ${response.status}`); | ||
| } | ||
| const data = await response.json(); | ||
| return data; | ||
| } catch (error) { | ||
| console.error('Failed to fetch recent activity:', error); | ||
| return { | ||
| success: false, | ||
| data: [], | ||
| message: 'Failed to load recent activity' | ||
| }; | ||
| } | ||
| }, | ||
|
|
||
| async getEarningsAnalytics(userId: string, dateRange?: Record<string, unknown>) { | ||
| const params = new URLSearchParams({ userId, ...dateRange }); | ||
| return apiUtils.request(`/dashboard/host/${userId}/earnings?${params}`); | ||
|
|
||
| getEarningsAnalytics: async (userId: string, dateRange?: Record<string, unknown>) => { | ||
| try { | ||
| const params = new URLSearchParams({ userId }); | ||
| if (dateRange) { | ||
| Object.entries(dateRange).forEach(([key, value]) => { | ||
| if (value) params.append(key, String(value)); | ||
| }); | ||
| } | ||
| const response = await fetch(`/api/analytics/earnings?${params}`); | ||
| if (!response.ok) { | ||
| throw new Error(`HTTP error! status: ${response.status}`); | ||
| } | ||
| const data = await response.json(); | ||
| return data; | ||
| } catch (error) { | ||
| console.error('Failed to fetch earnings analytics:', error); | ||
| return { | ||
| success: false, | ||
| data: [], | ||
| message: 'Failed to load earnings analytics' | ||
| }; | ||
| } | ||
| }, | ||
|
|
||
| async getBookingAnalytics( | ||
| getBookingAnalytics: async ( | ||
| userId: string, | ||
| userType: 'host' | 'tenant', | ||
| dateRange?: Record<string, unknown> | ||
| ) { | ||
| const params = new URLSearchParams({ userId, ...dateRange }); | ||
| return apiUtils.request(`/dashboard/${userType}/${userId}/bookings/analytics?${params}`); | ||
| ) => { | ||
| try { | ||
| const params = new URLSearchParams({ userId, userType }); | ||
| if (dateRange) { | ||
| Object.entries(dateRange).forEach(([key, value]) => { | ||
| if (value) params.append(key, String(value)); | ||
| }); | ||
| } | ||
| const response = await fetch(`/api/analytics/bookings?${params}`); | ||
| if (!response.ok) { | ||
| throw new Error(`HTTP error! status: ${response.status}`); | ||
| } | ||
| const data = await response.json(); | ||
| return data; | ||
| } catch (error) { | ||
| console.error('Failed to fetch booking analytics:', error); | ||
| return { | ||
| success: false, | ||
| data: [], | ||
| message: 'Failed to load booking analytics' | ||
| }; | ||
| } |
There was a problem hiding this comment.
Wallet/dashboard clients must reuse _apiCall
These new wallet/dashboard helpers invoke bare fetch('/api/...'), so they never hit API_BASE_URL, never send the bearer token, and your POST bodies go out without a JSON Content-Type. That’s a straight regression from the previous apiUtils.request flow—expect 401s and rejected payloads. Please pipe them through _apiCall (or apiUtils.request) so auth headers, credentials, and JSON defaults are restored.
- const response = await fetch(`/api/wallets/${userId}/balance`);
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- const data = await response.json();
- return data;
+ return await _apiCall(`/wallets/${userId}/balance`);- const response = await fetch(`/api/wallets/${userId}/deposit`, {
- method: 'POST',
- body: JSON.stringify({ amount, paymentMethod }),
- });
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- const data = await response.json();
- return data;
+ return await _apiCall(`/wallets/${userId}/deposit`, {
+ method: 'POST',
+ body: JSON.stringify({ amount, paymentMethod }),
+ });- const response = await fetch(`/api/analytics/overview?userId=${userId}&userType=${userType}`);
- if (!response.ok) {
- throw new Error(`HTTP error! status: ${response.status}`);
- }
- const data = await response.json();
- return data;
+ return await _apiCall(`/analytics/overview?userId=${userId}&userType=${userType}`);(Apply the same _apiCall substitution for the remaining wallet/dashboard functions.)
📝 Committable suggestion
‼️ 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.
| getWalletBalance: async (userId: string) => { | |
| try { | |
| const response = await fetch(`/api/wallets/${userId}/balance`); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| return data; | |
| } catch (error) { | |
| console.error('Failed to fetch wallet balance:', error); | |
| return { | |
| success: false, | |
| data: { balance: 0, currency: 'USD' }, | |
| message: 'Failed to load wallet balance' | |
| }; | |
| } | |
| }, | |
| async getTransactionHistory(userId: string, filters?: Record<string, unknown>) { | |
| const params = new URLSearchParams({ userId, ...filters }); | |
| return apiUtils.request(`/wallet/${userId}/transactions?${params}`); | |
| getTransactionHistory: async (userId: string, filters?: Record<string, unknown>) => { | |
| try { | |
| const params = new URLSearchParams({ userId }); | |
| if (filters) { | |
| Object.entries(filters).forEach(([key, value]) => { | |
| if (value) params.append(key, String(value)); | |
| }); | |
| } | |
| const response = await fetch(`/api/wallets/${userId}/transactions?${params}`); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| return data; | |
| } catch (error) { | |
| console.error('Failed to fetch transaction history:', error); | |
| return { | |
| success: false, | |
| data: [], | |
| message: 'Failed to load transaction history' | |
| }; | |
| } | |
| }, | |
| async addFunds(userId: string, amount: number, paymentMethod: string) { | |
| return apiUtils.request(`/wallet/${userId}/add-funds`, { | |
| method: 'POST', | |
| body: JSON.stringify({ amount, paymentMethod }), | |
| }); | |
| addFunds: async (userId: string, amount: number, paymentMethod: string) => { | |
| try { | |
| const response = await fetch(`/api/wallets/${userId}/deposit`, { | |
| method: 'POST', | |
| body: JSON.stringify({ amount, paymentMethod }), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| return data; | |
| } catch (error) { | |
| console.error('Failed to add funds:', error); | |
| throw new Error('Failed to process deposit'); | |
| } | |
| }, | |
| async withdrawFunds(userId: string, amount: number, accountDetails: Record<string, unknown>) { | |
| return apiUtils.request(`/wallet/${userId}/withdraw`, { | |
| method: 'POST', | |
| body: JSON.stringify({ amount, accountDetails }), | |
| }); | |
| withdrawFunds: async (userId: string, amount: number, accountDetails: Record<string, unknown>) => { | |
| try { | |
| const response = await fetch(`/api/wallets/${userId}/withdraw`, { | |
| method: 'POST', | |
| body: JSON.stringify({ amount, accountDetails }), | |
| }); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| return data; | |
| } catch (error) { | |
| console.error('Failed to withdraw funds:', error); | |
| throw new Error('Failed to process withdrawal'); | |
| } | |
| }, | |
| }; | |
| export const notificationAPI = { | |
| async getNotifications(userId: string, filters?: Record<string, unknown>) { | |
| const params = new URLSearchParams({ userId, ...filters }); | |
| return apiUtils.request(`/notifications?${params}`); | |
| }, | |
| async markAsRead(notificationId: string) { | |
| return apiUtils.request(`/notifications/${notificationId}/read`, { | |
| method: 'PUT', | |
| }); | |
| }, | |
| async markAllAsRead(userId: string) { | |
| return apiUtils.request(`/notifications/${userId}/read-all`, { | |
| method: 'PUT', | |
| }); | |
| }, | |
| async deleteNotification(notificationId: string) { | |
| return apiUtils.request(`/notifications/${notificationId}`, { | |
| method: 'DELETE', | |
| }); | |
| }, | |
| async deleteAllNotifications(userId: string) { | |
| return apiUtils.request(`/notifications/${userId}/delete-all`, { | |
| method: 'DELETE', | |
| {{ ... }} | |
| }); | |
| }, | |
| }; | |
| export const dashboardAPI = { | |
| async getDashboardStats(userId: string, userType: 'host' | 'tenant') { | |
| return apiUtils.request(`/dashboard/${userType}/${userId}/stats`); | |
| getDashboardStats: async (userId: string, userType: 'host' | 'tenant') => { | |
| try { | |
| const response = await fetch(`/api/analytics/overview?userId=${userId}&userType=${userType}`); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| return data; | |
| } catch (error) { | |
| console.error('Failed to fetch dashboard stats:', error); | |
| return { | |
| success: false, | |
| data: { | |
| totalBookings: 0, | |
| totalEarnings: 0, | |
| activeListings: 0, | |
| pendingRequests: 0 | |
| }, | |
| message: 'Failed to load dashboard stats' | |
| }; | |
| } | |
| }, | |
| async getRecentActivity(userId: string, userType: 'host' | 'tenant') { | |
| return apiUtils.request(`/dashboard/${userType}/${userId}/activity`); | |
| getRecentActivity: async (userId: string, userType: 'host' | 'tenant') => { | |
| try { | |
| const response = await fetch(`/api/activity/recent?userId=${userId}&userType=${userType}`); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| return data; | |
| } catch (error) { | |
| console.error('Failed to fetch recent activity:', error); | |
| return { | |
| success: false, | |
| data: [], | |
| message: 'Failed to load recent activity' | |
| }; | |
| } | |
| }, | |
| async getEarningsAnalytics(userId: string, dateRange?: Record<string, unknown>) { | |
| const params = new URLSearchParams({ userId, ...dateRange }); | |
| return apiUtils.request(`/dashboard/host/${userId}/earnings?${params}`); | |
| getEarningsAnalytics: async (userId: string, dateRange?: Record<string, unknown>) => { | |
| try { | |
| const params = new URLSearchParams({ userId }); | |
| if (dateRange) { | |
| Object.entries(dateRange).forEach(([key, value]) => { | |
| if (value) params.append(key, String(value)); | |
| }); | |
| } | |
| const response = await fetch(`/api/analytics/earnings?${params}`); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| return data; | |
| } catch (error) { | |
| console.error('Failed to fetch earnings analytics:', error); | |
| return { | |
| success: false, | |
| data: [], | |
| message: 'Failed to load earnings analytics' | |
| }; | |
| } | |
| }, | |
| async getBookingAnalytics( | |
| getBookingAnalytics: async ( | |
| userId: string, | |
| userType: 'host' | 'tenant', | |
| dateRange?: Record<string, unknown> | |
| ) { | |
| const params = new URLSearchParams({ userId, ...dateRange }); | |
| return apiUtils.request(`/dashboard/${userType}/${userId}/bookings/analytics?${params}`); | |
| ) => { | |
| try { | |
| const params = new URLSearchParams({ userId, userType }); | |
| if (dateRange) { | |
| Object.entries(dateRange).forEach(([key, value]) => { | |
| if (value) params.append(key, String(value)); | |
| }); | |
| } | |
| const response = await fetch(`/api/analytics/bookings?${params}`); | |
| if (!response.ok) { | |
| throw new Error(`HTTP error! status: ${response.status}`); | |
| } | |
| const data = await response.json(); | |
| return data; | |
| } catch (error) { | |
| console.error('Failed to fetch booking analytics:', error); | |
| return { | |
| success: false, | |
| data: [], | |
| message: 'Failed to load booking analytics' | |
| }; | |
| } | |
| getWalletBalance: async (userId: string) => { | |
| try { | |
| - const response = await fetch(`/api/wallets/${userId}/balance`); | |
| - if (!response.ok) { | |
| - throw new Error(`HTTP error! status: ${response.status}`); | |
| - } | |
| - const data = await response.json(); | |
| return await _apiCall(`/wallets/${userId}/balance`); | |
| } catch (error) { | |
| console.error('Failed to fetch wallet balance:', error); | |
| return { | |
| success: false, | |
| data: { balance: 0, currency: 'USD' }, | |
| message: 'Failed to load wallet balance' | |
| }; | |
| } | |
| }, | |
| addFunds: async (userId: string, amount: number, paymentMethod: string) => { | |
| try { | |
| - const response = await fetch(`/api/wallets/${userId}/deposit`, { | |
| - method: 'POST', | |
| - body: JSON.stringify({ amount, paymentMethod }), | |
| - }); | |
| - if (!response.ok) { | |
| - throw new Error(`HTTP error! status: ${response.status}`); | |
| - } | |
| - const data = await response.json(); | |
| return await _apiCall(`/wallets/${userId}/deposit`, { | |
| method: 'POST', | |
| body: JSON.stringify({ amount, paymentMethod }), | |
| }); | |
| } catch (error) { | |
| console.error('Failed to add funds:', error); | |
| throw new Error('Failed to process deposit'); | |
| } | |
| }, | |
| getDashboardStats: async (userId: string, userType: 'host' | 'tenant') => { | |
| try { | |
| - const response = await fetch(`/api/analytics/overview?userId=${userId}&userType=${userType}`); | |
| - if (!response.ok) { | |
| - throw new Error(`HTTP error! status: ${response.status}`); | |
| - } | |
| - const data = await response.json(); | |
| return await _apiCall(`/analytics/overview?userId=${userId}&userType=${userType}`); | |
| } catch (error) { | |
| console.error('Failed to fetch dashboard stats:', error); | |
| return { | |
| success: false, | |
| data: { | |
| totalBookings: 0, | |
| totalEarnings: 0, | |
| activeListings: 0, | |
| pendingRequests: 0 | |
| }, | |
| message: 'Failed to load dashboard stats' | |
| }; | |
| } | |
| }, |
🤖 Prompt for AI Agents
In apps/web/src/services/api.ts around lines 293 to 468, multiple wallet and
dashboard helpers call fetch(...) directly which bypasses API_BASE_URL, auth
bearer token, credentials and JSON headers; replace each direct fetch with the
shared helper _apiCall (or apiUtils.request) so requests use the base URL and
include auth/headers. For GETs build the query string or pass params to _apiCall
and call await _apiCall({ path: '/wallets/${userId}/balance' }) (or equivalent)
and return its parsed response; for POSTs include method:'POST' and body:
JSON.stringify(...) and ensure Content-Type: 'application/json' is set by
passing headers or letting _apiCall add it. Keep existing try/catch logic and
return shapes but delegate network call to _apiCall for all wallet and dashboard
functions (apply the same substitution to any remaining wallet/dashboard
helpers).
…ndpoints
- Fix profile endpoint URL from /api/profiles/{userId} to /api/profile
- Remove userId parameter from bookings endpoint call
- Use mock data for wallet transactions until endpoint is implemented
- Calculate dashboard stats from bookings data instead of analytics endpoint
- Add proper error handling and fallbacks for all endpoints
- Fix linting issues (template literals, any types, parseFloat usage)
Resolves Stellar-Rent#141 - Dashboard hooks now call existing backend endpoints
Pull Request | StellarRent
📝 Summary
Provide a brief description of what this PR accomplishes.
🔗 Related Issues
Closes #(issue number) (Replace with the actual issue number).
🔄 Changes Made
Provide a general description of the changes. Include any relevant background information or context to help reviewers understand the purpose of this PR.
🖼️ Current Output
Provide visual evidence of the changes:
🧪 Testing
If applicable, describe the tests performed. Include screenshots, test outputs, or any resources that help reviewers understand how the changes were tested.
✅ Testing Checklist
List any possible issues that might arise with this change.
🚀 Next Steps & Improvements
This change lays a solid foundation for further optimizations. Some areas that could benefit from future improvements include:
💬 Comments
Any additional context, questions, or considerations for reviewers.
Summary by CodeRabbit
New Features
Bug Fixes
Refactor