import React, { useState, useEffect } from "react"; import { Link } from "react-router"; import { rentalAPI, userAPI, stripeAPI } from "../services/api"; import { Rental, User } from "../types"; import StripeConnectOnboarding from "../components/StripeConnectOnboarding"; import EarningsStatus from "../components/EarningsStatus"; interface EarningsData { totalEarnings: number; pendingEarnings: number; completedEarnings: number; rentalsWithEarnings: Rental[]; } interface AccountStatus { detailsSubmitted: boolean; payoutsEnabled: boolean; } const EarningsDashboard: React.FC = () => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [earningsData, setEarningsData] = useState(null); const [userProfile, setUserProfile] = useState(null); const [accountStatus, setAccountStatus] = useState( null ); const [showOnboarding, setShowOnboarding] = useState(false); useEffect(() => { fetchUserProfile(); fetchEarningsData(); }, []); const fetchUserProfile = async () => { try { const response = await userAPI.getProfile(); setUserProfile(response.data); // If user has a Stripe account, fetch account status if (response.data.stripeConnectedAccountId) { await fetchAccountStatus(); } } catch (err) { console.error("Failed to fetch user profile:", err); } }; const fetchAccountStatus = async () => { try { const response = await stripeAPI.getAccountStatus(); setAccountStatus({ detailsSubmitted: response.data.detailsSubmitted, payoutsEnabled: response.data.payoutsEnabled, }); } catch (err) { console.error("Failed to fetch account status:", err); } }; const fetchEarningsData = async () => { try { // Get completed rentals where user is the owner const response = await rentalAPI.getListings(); const rentals = response.data || []; // Filter for completed rentals with earnings data const completedRentals = rentals.filter( (rental: Rental) => rental.status === "completed" && rental.payoutAmount ); // Calculate earnings - convert string values to numbers const totalEarnings = completedRentals.reduce( (sum: number, rental: Rental) => sum + parseFloat(rental.payoutAmount?.toString() || "0"), 0 ); const pendingEarnings = completedRentals .filter((rental: Rental) => rental.bankDepositStatus !== "paid") .reduce( (sum: number, rental: Rental) => sum + parseFloat(rental.payoutAmount?.toString() || "0"), 0 ); const completedEarnings = completedRentals .filter((rental: Rental) => rental.bankDepositStatus === "paid") .reduce( (sum: number, rental: Rental) => sum + parseFloat(rental.payoutAmount?.toString() || "0"), 0 ); setEarningsData({ totalEarnings, pendingEarnings, completedEarnings, rentalsWithEarnings: completedRentals, }); } catch (err: any) { setError(err.response?.data?.message || "Failed to fetch earnings data"); } finally { setLoading(false); } }; const handleSetupComplete = () => { setShowOnboarding(false); fetchUserProfile(); // Refresh user profile after setup fetchEarningsData(); }; if (loading) { return (
Loading...
); } const hasStripeAccount = !!userProfile?.stripeConnectedAccountId; const isOnboardingComplete = accountStatus?.detailsSubmitted ?? false; const payoutsEnabled = accountStatus?.payoutsEnabled ?? true; // Don't show setup card until we have account status (if user has a Stripe account) // This prevents the setup card from flashing briefly while fetching account status const accountStatusLoading = hasStripeAccount && accountStatus === null; // Show setup card if: no account, onboarding incomplete, or payouts disabled const showSetupCard = !accountStatusLoading && (!hasStripeAccount || !isOnboardingComplete || !payoutsEnabled); return (

Earnings

Manage your rental earnings and payment setup.{" "} Calculate what you can earn here {" "} or learn how payouts work.

{error && (
{error}
)} {/* Earnings Setup - show if not fully set up or payouts disabled */} {showSetupCard && (
Earnings Setup
setShowOnboarding(true)} />
)}
{/* Earnings Overview */}
Earnings Overview

${(earningsData?.totalEarnings || 0).toFixed(2)}

Total Earnings

${(earningsData?.pendingEarnings || 0).toFixed(2)}

Pending Earnings

${(earningsData?.completedEarnings || 0).toFixed(2)}

Paid Out

{/* Earnings History */}
Earnings History
{earningsData?.rentalsWithEarnings.length === 0 ? (

No completed rentals yet

) : (
{earningsData?.rentalsWithEarnings.map((rental) => ( ))}
Item Rental Period Total Amount Your Earnings Status
{rental.item?.name || "Item"} {new Date(rental.startDateTime).toLocaleString()}
to
{new Date(rental.endDateTime).toLocaleString()}
$ {parseFloat( rental.totalAmount?.toString() || "0" ).toFixed(2)} $ {parseFloat( rental.payoutAmount?.toString() || "0" ).toFixed(2)} {(() => { // Determine badge based on bank deposit and payout status let badgeClass = "bg-secondary"; let badgeLabel = "Pending"; let badgeTooltip = "Waiting for rental to complete or Stripe setup."; if (rental.bankDepositStatus === "paid") { badgeClass = "bg-success"; badgeLabel = "Deposited"; badgeTooltip = rental.bankDepositAt ? `Deposited to your bank on ${new Date( rental.bankDepositAt ).toLocaleDateString()}` : "Funds deposited to your bank account."; } else if ( rental.bankDepositStatus === "failed" ) { badgeClass = "bg-danger"; badgeLabel = "Deposit Failed"; badgeTooltip = "Bank deposit failed. Please check your Stripe dashboard."; } else if ( rental.bankDepositStatus === "in_transit" ) { badgeClass = "bg-info"; badgeLabel = "In Transit to Bank"; badgeTooltip = "Funds are on their way to your bank."; } else if (rental.payoutStatus === "completed") { badgeClass = "bg-info"; badgeLabel = "Transferred to Stripe"; badgeTooltip = "In your Stripe balance. Bank deposit in 2-7 business days."; } else if (rental.payoutStatus === "failed") { badgeClass = "bg-danger"; badgeLabel = "Transfer Failed"; badgeTooltip = "Transfer failed. We'll retry automatically."; } return ( {badgeLabel} ); })()}
)}
{/* Stripe Connect Onboarding Modal */} {showOnboarding && ( setShowOnboarding(false)} hasExistingAccount={hasStripeAccount} /> )}
); }; export default EarningsDashboard;