Skip to content

fix-141-dashboard-hooks-call-non-existent-api-endpoints#177

Merged
respp merged 2 commits intoStellar-Rent:mainfrom
Emmzyemms:fix-141-dashboard-hooks-call-non-existent-api-endpoints
Oct 4, 2025
Merged

fix-141-dashboard-hooks-call-non-existent-api-endpoints#177
respp merged 2 commits intoStellar-Rent:mainfrom
Emmzyemms:fix-141-dashboard-hooks-call-non-existent-api-endpoints

Conversation

@Emmzyemms
Copy link
Contributor

@Emmzyemms Emmzyemms commented Oct 3, 2025

StellarRent Logo

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:

  • For small changes: Screenshots.
  • For large changes: Video or Loom link.

🧪 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

  • Unit tests added/modified
  • Integration tests performed
  • Manual tests executed
  • All tests pass in CI/CD

⚠️ Potential Risks

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:

  • 🔹 Performance optimization
  • 🔹 Increased test coverage
  • 🔹 Potential user experience enhancements

💬 Comments

Any additional context, questions, or considerations for reviewers.

Summary by CodeRabbit

  • New Features

    • Booking details now load from the live API with validated IDs and mapped fields.
    • Dashboard adds richer stats, earnings, booking analytics, and recent activity; transactions temporarily mocked until endpoints exist.
    • Wallet actions (balance, history, add funds, withdraw) return clearer, structured results.
  • Bug Fixes

    • Clearer error messages and fallbacks for missing bookings, dashboard, and wallet data.
    • More reliable loading states and defaults to prevent blank or stale views.
  • Refactor

    • Unified fetch-based API handling with normalized responses and safer error handling; notification surface reduced.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 3, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Replaces 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

Cohort / File(s) Change summary
Hooks: Booking details
apps/web/src/hooks/useBookingDetails.ts
Replaced mock data with GET /api/bookings/{bookingId}; added isValidBookingId validation, explicit 404 and generic error handling, response JSON parsing and mapping to BookingData; removed artificial delays; preserved loading/error state semantics.
Hooks: Dashboard data
apps/web/src/hooks/useDashboard.ts
Switched to direct fetch calls for bookings, profile, transactions, and stats; added response.ok checks and JSON parsing; standardized error messages and defensive resets (arrays/null/default stats); maintained per-segment loading flags and refresh orchestration.
Services API: Fetch-based unification
apps/web/src/services/api.ts
Introduced generic _apiCall (headers, Authorization, credentials, 204 handling, non-OK extraction); refactored wallet/dashboard/notification endpoints to use explicit fetch + try/catch; returned standardized success/failure payloads and sane defaults; updated wallet/dashboard method signatures.

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
Loading
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
Loading
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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

Poem

I’m a rabbit on the network path, nibbling bugs away,
Mock carrots gone — real bytes brighten up the day.
IDs checked, errors penned, data mapped just so,
Dashboards, wallets, bookings — off to the real show! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title correctly highlights that dashboard hooks were updated to call existing API endpoints, which aligns with part of the changeset, but it also includes the issue ID prefix and overlooks the broader API and hook refactoring in this PR.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ce1985a and 620f1b0.

📒 Files selected for processing (1)
  • apps/web/src/hooks/useDashboard.ts (5 hunks)

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

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 client

All four new fetch('/api/...') calls now omit the Authorization header and API base handling that bookingAPI/profileAPI/walletAPI/dashboardAPI (via apiUtils.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, and fetchStats.)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5fa7fc1 and ce1985a.

📒 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)

Comment on lines +36 to 75
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 {
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.

Comment on lines +293 to +468
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'
};
}
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

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.

Suggested change
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
@respp respp merged commit 2d52e63 into Stellar-Rent:main Oct 4, 2025
@coderabbitai coderabbitai bot mentioned this pull request Oct 10, 2025
4 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants