Skip to content

Commit 7dcd930

Browse files
feat(packages): pegout flow (#314)
1 parent f3d2ee5 commit 7dcd930

File tree

6 files changed

+404
-16
lines changed

6 files changed

+404
-16
lines changed

routes/vault/src/components/Activities/Activities.tsx

Lines changed: 90 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,43 @@ import {
44
ActivityList,
55
type ActivityCardData,
66
type ActivityCardDetailItem,
7+
type ActivityCardActionButton,
78
ProviderItem,
89
} from "@babylonlabs-io/core-ui";
10+
import { useCallback, useState } from "react";
11+
import type { VaultActivity } from "../../mockData/vaultActivities";
912
import { BorrowFlow } from "../BorrowFlow";
13+
import { RepayFlow } from "../RepayFlow";
1014
import { useActivitiesState } from "./useActivitiesState";
1115

16+
function getPrimaryAction(
17+
activity: VaultActivity,
18+
hasBorrowed: boolean,
19+
isActive: boolean,
20+
onBorrow: (activity: VaultActivity) => void,
21+
onRepay: (activity: VaultActivity) => void
22+
): ActivityCardActionButton | undefined {
23+
if (hasBorrowed) {
24+
return {
25+
label: "Repay and Withdraw BTC",
26+
onClick: () => onRepay(activity),
27+
variant: "outlined",
28+
fullWidth: true,
29+
};
30+
}
31+
32+
if (isActive && !hasBorrowed) {
33+
return {
34+
label: "Borrow USDC",
35+
onClick: () => onBorrow(activity),
36+
variant: "outlined",
37+
fullWidth: true,
38+
};
39+
}
40+
41+
return undefined;
42+
}
43+
1244
export function Activities() {
1345
const {
1446
activities,
@@ -21,6 +53,21 @@ export function Activities() {
2153
refetchActivities,
2254
} = useActivitiesState();
2355

56+
// Repay flow state
57+
const [repayFlowOpen, setRepayFlowOpen] = useState(false);
58+
const [selectedRepayActivity, setSelectedRepayActivity] = useState<VaultActivity | null>(null);
59+
60+
const handleRepayAndWithdraw = useCallback((activity: VaultActivity) => {
61+
console.log('[Activities] Repay and withdraw for:', activity.id);
62+
setSelectedRepayActivity(activity);
63+
setRepayFlowOpen(true);
64+
}, []);
65+
66+
const handleRepayFlowClose = useCallback(() => {
67+
setRepayFlowOpen(false);
68+
setSelectedRepayActivity(null);
69+
}, []);
70+
2471
console.log('[Activities] activities:', activities);
2572
console.log('[Activities] activities.length:', activities.length);
2673

@@ -66,30 +113,46 @@ export function Activities() {
66113
};
67114

68115
// Check if user has already borrowed (has borrow shares > 0)
69-
const hasBorrowed = activity.morphoPosition && activity.morphoPosition.borrowShares > 0n;
116+
const hasBorrowed = !!(activity.morphoPosition && activity.morphoPosition.borrowShares > 0n);
70117

71118
// Only active vaults can borrow
72119
const isActive = activity.status.variant === 'active';
73120

74-
// Build details array, adding borrowing data if available
121+
// Build main details array
75122
const details: ActivityCardDetailItem[] = [statusDetail, providersDetail];
76123

77-
// Add borrowing details if user has borrowed
124+
// Build optional details array for loan information (shown in separate section)
125+
const optionalDetails: ActivityCardDetailItem[] = [];
126+
78127
if (activity.borrowingData) {
79-
details.push({
80-
label: "Borrowed",
128+
// Add loan details section header
129+
optionalDetails.push({
130+
label: (
131+
<div className="space-y-1">
132+
<div className="text-base font-semibold text-accent-primary">Loan Details</div>
133+
<div className="text-xs text-accent-secondary">Your current loan</div>
134+
</div>
135+
),
136+
value: "",
137+
});
138+
139+
// Add loan amount
140+
optionalDetails.push({
141+
label: "Loan",
81142
value: `${activity.borrowingData.borrowedAmount} ${activity.borrowingData.borrowedSymbol}`,
82143
});
83-
// Only show LTV if it's calculated (non-zero)
84-
// TODO: Implement BTC price oracle to calculate accurate current LTV
144+
145+
// Add LTV (only if calculated)
85146
if (activity.borrowingData.currentLTV > 0) {
86-
details.push({
87-
label: "Current LTV",
147+
optionalDetails.push({
148+
label: "LTV",
88149
value: `${activity.borrowingData.currentLTV}%`,
89150
});
90151
}
91-
details.push({
92-
label: "Max LTV (LLTV)",
152+
153+
// Add liquidation LTV
154+
optionalDetails.push({
155+
label: "Liquidation LTV",
93156
value: `${activity.borrowingData.maxLTV}%`,
94157
});
95158
}
@@ -99,11 +162,15 @@ export function Activities() {
99162
icon: activity.collateral.icon,
100163
iconAlt: activity.collateral.symbol,
101164
details,
102-
// Show borrow button if vault is active and user hasn't borrowed yet
103-
primaryAction: (isActive && !hasBorrowed) ? {
104-
label: activity.action.label,
105-
onClick: () => handleActivityBorrow(activity),
106-
} : undefined,
165+
optionalDetails: optionalDetails.length > 0 ? optionalDetails : undefined,
166+
// Use helper function for cleaner conditional button logic
167+
primaryAction: getPrimaryAction(
168+
activity,
169+
hasBorrowed,
170+
isActive,
171+
handleActivityBorrow,
172+
handleRepayAndWithdraw
173+
),
107174
};
108175
});
109176

@@ -123,6 +190,13 @@ export function Activities() {
123190
onClose={handleBorrowFlowClose}
124191
onBorrowSuccess={refetchActivities}
125192
/>
193+
194+
<RepayFlow
195+
activity={selectedRepayActivity}
196+
isOpen={repayFlowOpen}
197+
onClose={handleRepayFlowClose}
198+
onRepaySuccess={refetchActivities}
199+
/>
126200
</>
127201
);
128202
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { useEffect } from "react";
2+
import type { VaultActivity } from "../../mockData/vaultActivities";
3+
import { RepaySignModal } from "../modals/RepaySignModal";
4+
import { RepaySuccessModal } from "../modals/RepaySuccessModal";
5+
import { useRepayFlowState } from "./useRepayFlowState";
6+
7+
interface RepayFlowProps {
8+
activity: VaultActivity | null;
9+
isOpen: boolean;
10+
onClose: () => void;
11+
onRepaySuccess?: () => void;
12+
}
13+
14+
export function RepayFlow({ activity, isOpen, onClose, onRepaySuccess }: RepayFlowProps) {
15+
const {
16+
signModalOpen,
17+
successModalOpen,
18+
startRepayFlow,
19+
handleSignSuccess,
20+
handleSignModalClose,
21+
handleSuccessClose,
22+
} = useRepayFlowState();
23+
24+
// Start the flow when opened with an activity
25+
useEffect(() => {
26+
if (isOpen && activity) {
27+
startRepayFlow(activity);
28+
}
29+
}, [isOpen, activity, startRepayFlow]);
30+
31+
const handleFinalSuccess = async () => {
32+
handleSuccessClose();
33+
34+
if (onRepaySuccess) {
35+
await onRepaySuccess();
36+
}
37+
38+
onClose();
39+
};
40+
41+
if (!activity) return null;
42+
43+
const repayAmount = activity.borrowingData
44+
? `${activity.borrowingData.borrowedAmount} ${activity.borrowingData.borrowedSymbol}`
45+
: "0 USDC";
46+
const btcAmount = activity.collateral.amount;
47+
48+
return (
49+
<>
50+
{/* Repay Sign Modal */}
51+
<RepaySignModal
52+
open={signModalOpen}
53+
onClose={handleSignModalClose}
54+
onSuccess={handleSignSuccess}
55+
pegInTxHash={activity.txHash}
56+
/>
57+
58+
{/* Repay Success Modal */}
59+
<RepaySuccessModal
60+
open={successModalOpen}
61+
onClose={handleFinalSuccess}
62+
repayAmount={repayAmount}
63+
btcAmount={btcAmount}
64+
/>
65+
</>
66+
);
67+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { RepayFlow } from './RepayFlow';
2+
export { useRepayFlowState } from './useRepayFlowState';
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { useState, useCallback } from "react";
2+
import type { VaultActivity } from "../../mockData/vaultActivities";
3+
4+
export function useRepayFlowState() {
5+
// Modal states
6+
const [signModalOpen, setSignModalOpen] = useState(false);
7+
const [successModalOpen, setSuccessModalOpen] = useState(false);
8+
9+
// Selected activity
10+
const [selectedActivity, setSelectedActivity] = useState<VaultActivity | null>(null);
11+
12+
// Start the repay flow with an activity
13+
const startRepayFlow = useCallback((activity: VaultActivity) => {
14+
setSelectedActivity(activity);
15+
setSignModalOpen(true); // Go straight to sign modal (no input needed for repay)
16+
}, []);
17+
18+
// Handle signing success
19+
const handleSignSuccess = useCallback(() => {
20+
setSignModalOpen(false); // Close sign modal
21+
setSuccessModalOpen(true); // Open success modal
22+
}, []);
23+
24+
// Handle sign modal close
25+
const handleSignModalClose = useCallback(() => {
26+
setSignModalOpen(false);
27+
}, []);
28+
29+
// Handle success modal close
30+
const handleSuccessClose = useCallback(() => {
31+
setSuccessModalOpen(false);
32+
setSelectedActivity(null);
33+
}, []);
34+
35+
return {
36+
signModalOpen,
37+
successModalOpen,
38+
selectedActivity,
39+
startRepayFlow,
40+
handleSignSuccess,
41+
handleSignModalClose,
42+
handleSuccessClose,
43+
};
44+
}

0 commit comments

Comments
 (0)