Added UI for loan repayment process
This commit is contained in:
parent
59f9eec417
commit
91c4435649
@ -794,6 +794,7 @@ h3 {
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
color: rgba(var(--text-color), 0.8);
|
color: rgba(var(--text-color), 0.8);
|
||||||
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
.loan-process__type::before {
|
.loan-process__type::before {
|
||||||
content: "";
|
content: "";
|
||||||
|
|||||||
2
css/main.min.css
vendored
2
css/main.min.css
vendored
File diff suppressed because one or more lines are too long
@ -748,6 +748,7 @@ h3 {
|
|||||||
position: relative;
|
position: relative;
|
||||||
padding-bottom: 0.5rem;
|
padding-bottom: 0.5rem;
|
||||||
color: rgba(var(--text-color), 0.8);
|
color: rgba(var(--text-color), 0.8);
|
||||||
|
margin-right: auto;
|
||||||
&::before {
|
&::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
698
index.html
698
index.html
@ -645,299 +645,405 @@
|
|||||||
},
|
},
|
||||||
loanProcess(details = {}) {
|
loanProcess(details = {}) {
|
||||||
const {
|
const {
|
||||||
|
type,
|
||||||
loanOpeningProcessID,
|
loanOpeningProcessID,
|
||||||
borrower, isBorrower, coborrower, isCoborrower, lender, isLender, loanAmount, policyID,
|
borrower, isBorrower, coborrower, isCoborrower, lender, isLender, loanAmount, policyID,
|
||||||
initiationTime, loanRequestTime, loanResponseTime, collateralLockAckTime, loanIssuedTime,
|
initiationTime, loanRequestTime, loanResponseTime, collateralLockAckTime, loanIssuedTime,
|
||||||
hasProvidedCollateral, hasAgreedToLend, hasRequestedCollateralLock, hasLockedCollateral, hasIssuedLoan, hasRequestedCollateralRefund,
|
hasProvidedCollateral, hasAgreedToLend, hasRequestedCollateralLock, hasLockedCollateral, hasIssuedLoan, hasRequestedCollateralRefund,
|
||||||
collateralRequestID, loanRequestID, loanResponseID, collateralLockRequestID, collateralLockAckID, loanID
|
collateralRequestID, loanRequestID, loanResponseID, collateralLockRequestID, collateralLockAckID, loanID, closingTxID,
|
||||||
|
loanClosedAckTime, loanClosedAckID, hasPaidLoan, unlockCollateralRequestTime, unlockCollateralRequestID, hasRequestedCollateralUnlock, unlockCollateralAckTime, unlockCollateralAckID, hasRefundedCollateral
|
||||||
} = details
|
} = details
|
||||||
return Component(() => {
|
if (type === 'loanOpening') {
|
||||||
const [verifyingCollateral, setVerifyCollateral] = useState(false)
|
return Component(() => {
|
||||||
let collateralAmount = 0
|
const [verifyingCollateral, setVerifyCollateral] = useState(false)
|
||||||
if (!isLender)
|
let collateralAmount = 0
|
||||||
collateralAmount = btcMortgage.util.calcRequiredCollateral((loanAmount / floGlobals.btcRate), btcMortgage.policies[policyID]?.loan_collateral_ratio) || 0
|
if (!isLender)
|
||||||
async function verifyCollateral(e) {
|
collateralAmount = btcMortgage.util.calcRequiredCollateral((loanAmount / floGlobals.btcRate), btcMortgage.policies[policyID]?.loan_collateral_ratio) || 0
|
||||||
const confirmation = await getConfirmation('Verify collateral?', { message: `You are about to check for ${formatAmount(collateralAmount)} as collateral. Continue?`, confirmText: 'Verify', cancelText: 'Cancel' })
|
async function verifyCollateral(e) {
|
||||||
if (!confirmation)
|
const confirmation = await getConfirmation('Verify collateral?', { message: `You are about to check for ${formatAmount(collateralAmount)} as collateral. Continue?`, confirmText: 'Verify', cancelText: 'Cancel' })
|
||||||
return;
|
|
||||||
setVerifyCollateral(true)
|
|
||||||
try {
|
|
||||||
await btcMortgage.requestLoan(collateralRequestID, borrower)
|
|
||||||
notify('Collateral verified successfully', 'success')
|
|
||||||
} catch (err) {
|
|
||||||
notify(err, 'error')
|
|
||||||
setVerifyCollateral(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const [lockingCollateral, setLockingCollateral] = useState(false)
|
|
||||||
async function lockCollateral() {
|
|
||||||
const confirmation = await getConfirmation('Lock collateral?', { message: `You are about to lock ${formatAmount(collateralAmount)} as collateral. Continue?`, confirmText: 'Lock', cancelText: 'Cancel' })
|
|
||||||
if (!confirmation)
|
|
||||||
return;
|
|
||||||
try {
|
|
||||||
setLockingCollateral(true)
|
|
||||||
let collateralLockID = collateralLockRequestID
|
|
||||||
if (!collateralLockID) {
|
|
||||||
const { vectorClock } = await btcMortgage.requestCollateralLock(loanResponseID, coborrower, lender, await floDapps.user.private)
|
|
||||||
collateralLockID = vectorClock
|
|
||||||
}
|
|
||||||
await btcMortgage.lockCollateral(collateralLockID, borrower, lender, await floDapps.user.private)
|
|
||||||
notify('Collateral locked successfully', 'success')
|
|
||||||
router.routeTo(location.hash)
|
|
||||||
} catch (err) {
|
|
||||||
notify(err, 'error')
|
|
||||||
setLockingCollateral(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function requestCollateralLock() {
|
|
||||||
btcMortgage.requestCollateralLock(loanResponseID, coborrower, lender, await floDapps.user.private).then(() => {
|
|
||||||
notify('Collateral lock request sent successfully', 'success')
|
|
||||||
router.routeTo(location.hash)
|
|
||||||
}).catch(err => {
|
|
||||||
notify(err, 'error')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const [isIssuingLoan, setIsIssuingLoan] = useState(false)
|
|
||||||
const [failSafe, setFailSafe] = useState(null)
|
|
||||||
const [retringFailSafe, setRetryingFailSafe] = useState(false)
|
|
||||||
async function issueLoan() {
|
|
||||||
try {
|
|
||||||
const confirmation = await getConfirmation('Issue loan?', { message: `You are about to issue ${formatAmount(loanAmount)} as loan. Continue?`, confirmText: 'Issue', cancelText: 'Cancel' })
|
|
||||||
if (!confirmation)
|
if (!confirmation)
|
||||||
return;
|
return;
|
||||||
setIsIssuingLoan(true)
|
setVerifyCollateral(true)
|
||||||
await btcMortgage.sendLoanAmount(collateralLockAckID, borrower, coborrower, await floDapps.user.private)
|
try {
|
||||||
notify('Loan issued successfully', 'success')
|
await btcMortgage.requestLoan(collateralRequestID, borrower)
|
||||||
} catch (err) {
|
notify('Collateral verified successfully', 'success')
|
||||||
notify(err.message || err, 'error')
|
} catch (err) {
|
||||||
console.error(err)
|
notify(err, 'error')
|
||||||
if (err.fail_safe)
|
setVerifyCollateral(false)
|
||||||
setFailSafe(err.fail_safe)
|
|
||||||
} finally {
|
|
||||||
setIsIssuingLoan(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async function retryFailSafe() {
|
|
||||||
try {
|
|
||||||
setRetryingFailSafe(true)
|
|
||||||
await btcOperator.retryFailSafe(failSafe, await floDapps.user.private)
|
|
||||||
notify('Loan issued successfully', 'success')
|
|
||||||
} catch (err) {
|
|
||||||
notify(err, 'error')
|
|
||||||
} finally {
|
|
||||||
setRetryingFailSafe(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// check if loan hasn't been issued even after 24 hours of collateral lock
|
|
||||||
let hasLockedCollateralForMoreThan24Hours = false
|
|
||||||
useEffect(() => {
|
|
||||||
if (isCoborrower && hasLockedCollateral && !hasIssuedLoan) {
|
|
||||||
const timeDiff = Date.now() - collateralLockAckTime
|
|
||||||
if (timeDiff > 86400000) {
|
|
||||||
hasLockedCollateralForMoreThan24Hours = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [hasLockedCollateral, hasIssuedLoan])
|
const [lockingCollateral, setLockingCollateral] = useState(false)
|
||||||
const [requestingCollateralRefund, setRequestingCollateralRefund] = useState(false)
|
async function lockCollateral() {
|
||||||
async function requestCollateralRefund() {
|
const confirmation = await getConfirmation('Lock collateral?', { message: `You are about to lock ${formatAmount(collateralAmount)} as collateral. Continue?`, confirmText: 'Lock', cancelText: 'Cancel' })
|
||||||
const confirmation = await getConfirmation('Request collateral refund?', { message: `You are about to request for collateral refund. Continue?`, confirmText: 'Request', cancelText: 'Cancel' })
|
if (!confirmation)
|
||||||
if (!confirmation)
|
return;
|
||||||
return;
|
try {
|
||||||
try {
|
setLockingCollateral(true)
|
||||||
setRequestingCollateralRefund(true)
|
let collateralLockID = collateralLockRequestID
|
||||||
await btcMortgage.requestBanker.refundCollateral(collateralLockAckID, borrower, lender, await floDapps.user.private)
|
if (!collateralLockID) {
|
||||||
notify('Collateral refund requested successfully', 'success')
|
const { vectorClock } = await btcMortgage.requestCollateralLock(loanResponseID, coborrower, lender, await floDapps.user.private)
|
||||||
} catch (err) {
|
collateralLockID = vectorClock
|
||||||
notify(err, 'error')
|
}
|
||||||
} finally {
|
await btcMortgage.lockCollateral(collateralLockID, borrower, lender, await floDapps.user.private)
|
||||||
setRequestingCollateralRefund(false)
|
notify('Collateral locked successfully', 'success')
|
||||||
|
router.routeTo(location.hash)
|
||||||
|
} catch (err) {
|
||||||
|
notify(err, 'error')
|
||||||
|
setLockingCollateral(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
async function requestCollateralLock() {
|
||||||
function showLoanRequestDetails() {
|
btcMortgage.requestCollateralLock(loanResponseID, coborrower, lender, await floDapps.user.private).then(() => {
|
||||||
renderElem(getRef('request_details_content'), html`
|
notify('Collateral lock request sent successfully', 'success')
|
||||||
<div class="grid gap-0-3">
|
router.routeTo(location.hash)
|
||||||
<p>Loan amount</p>
|
}).catch(err => {
|
||||||
<b>${formatAmount(loanAmount, 'usd')}</b>
|
notify(err, 'error')
|
||||||
</div>
|
})
|
||||||
<div class="grid gap-0-3">
|
}
|
||||||
<p>Collateral amount</p>
|
|
||||||
<b>${formatAmount(collateralAmount)}</b>
|
const [isIssuingLoan, setIsIssuingLoan] = useState(false)
|
||||||
</div>
|
const [failSafe, setFailSafe] = useState(null)
|
||||||
<div class="grid gap-0-3">
|
const [retringFailSafe, setRetryingFailSafe] = useState(false)
|
||||||
<p>Duration</p>
|
async function issueLoan() {
|
||||||
<b>${btcMortgage.policies[policyID]?.duration}</b>
|
try {
|
||||||
</div>
|
const confirmation = await getConfirmation('Issue loan?', { message: `You are about to issue ${formatAmount(loanAmount)} as loan. Continue?`, confirmText: 'Issue', cancelText: 'Cancel' })
|
||||||
<div class="grid gap-0-3">
|
if (!confirmation)
|
||||||
<p>Interest</p>
|
return;
|
||||||
<b>${btcMortgage.policies[policyID]?.interest * 100}% p.a</b>
|
setIsIssuingLoan(true)
|
||||||
</div>
|
await btcMortgage.sendLoanAmount(collateralLockAckID, borrower, coborrower, await floDapps.user.private)
|
||||||
<div class="grid gap-0-3">
|
notify('Loan issued successfully', 'success')
|
||||||
<p>Borrower</p>
|
} catch (err) {
|
||||||
<sm-copy value=${getBtcAddress(borrower)}>
|
notify(err.message || err, 'error')
|
||||||
<b>${getBtcAddress(borrower)}</b>
|
console.error(err)
|
||||||
</sm-copy>
|
if (err.fail_safe)
|
||||||
</div>
|
setFailSafe(err.fail_safe)
|
||||||
${!floCrypto.isSameAddr(borrower, coborrower) ? html`
|
} finally {
|
||||||
|
setIsIssuingLoan(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async function retryFailSafe() {
|
||||||
|
try {
|
||||||
|
setRetryingFailSafe(true)
|
||||||
|
await btcOperator.retryFailSafe(failSafe, await floDapps.user.private)
|
||||||
|
notify('Loan issued successfully', 'success')
|
||||||
|
} catch (err) {
|
||||||
|
notify(err, 'error')
|
||||||
|
} finally {
|
||||||
|
setRetryingFailSafe(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check if loan hasn't been issued even after 24 hours of collateral lock
|
||||||
|
let hasLockedCollateralForMoreThan24Hours = false
|
||||||
|
useEffect(() => {
|
||||||
|
if (isCoborrower && hasLockedCollateral && !hasIssuedLoan) {
|
||||||
|
const timeDiff = Date.now() - collateralLockAckTime
|
||||||
|
if (timeDiff > 86400000) {
|
||||||
|
hasLockedCollateralForMoreThan24Hours = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [hasLockedCollateral, hasIssuedLoan])
|
||||||
|
const [requestingCollateralRefund, setRequestingCollateralRefund] = useState(false)
|
||||||
|
async function requestCollateralRefund() {
|
||||||
|
const confirmation = await getConfirmation('Request collateral refund?', { message: `You are about to request for collateral refund. Continue?`, confirmText: 'Request', cancelText: 'Cancel' })
|
||||||
|
if (!confirmation)
|
||||||
|
return;
|
||||||
|
try {
|
||||||
|
setRequestingCollateralRefund(true)
|
||||||
|
await btcMortgage.requestBanker.refundCollateral(collateralLockAckID, borrower, lender, await floDapps.user.private)
|
||||||
|
notify('Collateral refund requested successfully', 'success')
|
||||||
|
} catch (err) {
|
||||||
|
notify(err, 'error')
|
||||||
|
} finally {
|
||||||
|
setRequestingCollateralRefund(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function showLoanRequestDetails() {
|
||||||
|
renderElem(getRef('request_details_content'), html`
|
||||||
<div class="grid gap-0-3">
|
<div class="grid gap-0-3">
|
||||||
<p>Collateral provider</p>
|
<p>Loan amount</p>
|
||||||
<sm-copy value=${getBtcAddress(coborrower)}>
|
<b>${formatAmount(loanAmount, 'usd')}</b>
|
||||||
<b>${getBtcAddress(coborrower)}</b>
|
</div>
|
||||||
|
<div class="grid gap-0-3">
|
||||||
|
<p>Collateral amount</p>
|
||||||
|
<b>${formatAmount(collateralAmount)}</b>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-0-3">
|
||||||
|
<p>Duration</p>
|
||||||
|
<b>${btcMortgage.policies[policyID]?.duration}</b>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-0-3">
|
||||||
|
<p>Interest</p>
|
||||||
|
<b>${btcMortgage.policies[policyID]?.interest * 100}% p.a</b>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-0-3">
|
||||||
|
<p>Borrower</p>
|
||||||
|
<sm-copy value=${getBtcAddress(borrower)}>
|
||||||
|
<b>${getBtcAddress(borrower)}</b>
|
||||||
</sm-copy>
|
</sm-copy>
|
||||||
</div>
|
</div>
|
||||||
`: ''}
|
${!floCrypto.isSameAddr(borrower, coborrower) ? html`
|
||||||
`)
|
<div class="grid gap-0-3">
|
||||||
openPopup('request_details_popup')
|
<p>Collateral provider</p>
|
||||||
}
|
<sm-copy value=${getBtcAddress(coborrower)}>
|
||||||
return html`
|
<b>${getBtcAddress(coborrower)}</b>
|
||||||
<li class="loan-process">
|
</sm-copy>
|
||||||
<div class="flex align-center gap-1 space-between">
|
</div>
|
||||||
${isBorrower ? html`
|
`: ''}
|
||||||
<h4 class="loan-process__type">Borrowing</h4>
|
`)
|
||||||
`: html`
|
openPopup('request_details_popup')
|
||||||
${isCoborrower ? html`
|
}
|
||||||
|
return html`
|
||||||
|
<li class="loan-process">
|
||||||
|
<div class="flex align-center gap-1 space-between">
|
||||||
|
${isBorrower ? html`
|
||||||
|
<h4 class="loan-process__type">Borrowing</h4>
|
||||||
|
`: html`
|
||||||
|
${isCoborrower ? html`
|
||||||
<h4 class="loan-process__type">Co-borrowing</h4>
|
<h4 class="loan-process__type">Co-borrowing</h4>
|
||||||
`: html`
|
`: html`
|
||||||
<h4 class="loan-process__type">Lending</h4>
|
<h4 class="loan-process__type">Lending</h4>
|
||||||
`
|
`}
|
||||||
}
|
`}
|
||||||
`}
|
<button class="button button--colored button--small" onclick=${showLoanRequestDetails}>View details</button>
|
||||||
<button class="button button--colored button--small" onclick=${showLoanRequestDetails}>View details</button>
|
</div>
|
||||||
</div>
|
<ul>
|
||||||
<ul>
|
${!isLender ? html`
|
||||||
${!isLender ? html`
|
<li class=${`${hasProvidedCollateral ? 'done' : ''}`}>
|
||||||
<li class=${`${hasProvidedCollateral ? 'done' : ''}`}>
|
<div class="progress">
|
||||||
|
<div class="circle"></div>
|
||||||
|
<div class="line"></div>
|
||||||
|
</div>
|
||||||
|
<div class="details">
|
||||||
|
<h4>Initiated loan request</h4>
|
||||||
|
${hasProvidedCollateral ? html`
|
||||||
|
<time>${getFormattedTime(initiationTime)}</time>
|
||||||
|
`: html`
|
||||||
|
${isCoborrower ? html`
|
||||||
|
<p>Verify that you have <b>${formatAmount(collateralAmount)}</b> as collateral</p>
|
||||||
|
<button class="button button--primary margin-right-auto" disabled=${verifyingCollateral} onclick=${verifyCollateral}>
|
||||||
|
${verifyingCollateral ? html`
|
||||||
|
Verifying collateral
|
||||||
|
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
||||||
|
`: html`
|
||||||
|
Verify collateral
|
||||||
|
`}
|
||||||
|
</button>
|
||||||
|
`: html`
|
||||||
|
<p>Waiting for Co-Borrower to confirm collateral availability</p>
|
||||||
|
`}
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
`: ''}
|
||||||
|
<li class=${`${hasAgreedToLend ? 'done' : ''}`}>
|
||||||
<div class="progress">
|
<div class="progress">
|
||||||
<div class="circle"></div>
|
<div class="circle"></div>
|
||||||
<div class="line"></div>
|
<div class="line"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="details">
|
<div class="details">
|
||||||
<h4>Initiated loan request</h4>
|
${hasAgreedToLend ? html`
|
||||||
${hasProvidedCollateral ? html`
|
<h4>Started lending process</h4>
|
||||||
<time>${getFormattedTime(initiationTime)}</time>
|
<time>${getFormattedTime(loanResponseTime)}</time>
|
||||||
`: html`
|
`: html`
|
||||||
${isCoborrower ? html`
|
<h4>Waiting for a lender</h4>
|
||||||
<p>Verify that you have <b>${formatAmount(collateralAmount)}</b> as collateral</p>
|
<p>Your loan request has been posted to the marketplace. Waiting for a lender to start lending process.</p>
|
||||||
<button class="button button--primary margin-right-auto" disabled=${verifyingCollateral} onclick=${verifyCollateral}>
|
`}
|
||||||
${verifyingCollateral ? html`
|
</div>
|
||||||
Verifying collateral
|
</li>
|
||||||
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
<li class=${`${hasLockedCollateral ? 'done' : ''}`}>
|
||||||
`: html`
|
<div class="progress">
|
||||||
Verify collateral
|
<div class="circle"></div>
|
||||||
`}
|
<div class="line"></div>
|
||||||
</button>
|
</div>
|
||||||
|
<div class="details">
|
||||||
|
${hasLockedCollateral ? html`
|
||||||
|
<h4>Collateral locked</h4>
|
||||||
|
${hasLockedCollateralForMoreThan24Hours ? html`
|
||||||
|
${hasRequestedCollateralRefund ? html`
|
||||||
|
<p>Collateral refund requested. It'll be refunded within 24 hrs.</p>
|
||||||
|
`: html`
|
||||||
|
<p>
|
||||||
|
Loan has not been issued even after 24 hours of collateral lock.
|
||||||
|
You can request for collateral refund.
|
||||||
|
</p>
|
||||||
|
<button class="button button--primary margin-right-auto" disabled=${requestCollateralRefund} onclick=${requestCollateralRefund}>
|
||||||
|
${requestingCollateralRefund ? html`
|
||||||
|
Requesting collateral refund
|
||||||
|
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
||||||
|
`: html`
|
||||||
|
Request collateral refund
|
||||||
|
`}
|
||||||
|
</button>
|
||||||
|
`}
|
||||||
`: html`
|
`: html`
|
||||||
<p>Waiting for Co-Borrower to confirm collateral availability</p>
|
<time>${getFormattedTime(collateralLockAckTime)}</time>
|
||||||
|
`}
|
||||||
|
`: html`
|
||||||
|
<h4>Waiting for collateral to be locked</h4>
|
||||||
|
<p>${isCoborrower ? 'You need' : 'Loan co-borrower needs'} to lock collateral before loan can be issued.</p>
|
||||||
|
${hasAgreedToLend && (isBorrower || isCoborrower) ? html`
|
||||||
|
${floCrypto.isSameAddr(borrower, coborrower) ? html`
|
||||||
|
<button class="button button--primary margin-right-auto" disabled=${lockingCollateral} onclick=${lockCollateral}>
|
||||||
|
${lockingCollateral ? html`
|
||||||
|
Locking collateral
|
||||||
|
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
||||||
|
`: html`
|
||||||
|
Lock collateral
|
||||||
|
`}
|
||||||
|
</button>
|
||||||
|
`: html`
|
||||||
|
<button class="button button--primary margin-right-auto" onclick=${requestCollateralLock}>Request collateral lock</button>
|
||||||
|
`}
|
||||||
|
`: ''}
|
||||||
|
`}
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class=${`${hasIssuedLoan ? 'done' : ''}`}>
|
||||||
|
<div class="progress">
|
||||||
|
<div class="circle"></div>
|
||||||
|
</div>
|
||||||
|
<div class="details">
|
||||||
|
${hasIssuedLoan ? html`
|
||||||
|
<h4>Loan issued</h4>
|
||||||
|
<time>${getFormattedTime(loanIssuedTime)}</time>
|
||||||
|
`: html`
|
||||||
|
${hasLockedCollateral && isLender ? html`
|
||||||
|
${failSafe ? html`
|
||||||
|
<h4>Loan failed to issue</h4>
|
||||||
|
<button class="button button--primary margin-right-auto" disabled=${retringFailSafe} onclick=${retryFailSafe}>
|
||||||
|
${retringFailSafe ? html`
|
||||||
|
Retrying
|
||||||
|
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
||||||
|
`: html`
|
||||||
|
Retry
|
||||||
|
`}
|
||||||
|
</button>
|
||||||
|
`: html`
|
||||||
|
<h4>Loan is ready to be issued</h4>
|
||||||
|
<button class="button button--primary margin-right-auto" disabled=${isIssuingLoan} onclick=${issueLoan}>
|
||||||
|
${isIssuingLoan ? html`
|
||||||
|
Issuing loan
|
||||||
|
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
||||||
|
`: html`
|
||||||
|
Issue loan
|
||||||
|
`}
|
||||||
|
</button>
|
||||||
|
`}
|
||||||
|
`: html`
|
||||||
|
<h4>Waiting for loan to be issued</h4>
|
||||||
`}
|
`}
|
||||||
`}
|
`}
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
`: ''}
|
</ul>
|
||||||
<li class=${`${hasAgreedToLend ? 'done' : ''}`}>
|
<p class="margin-left-auto">Request ID: #${loanOpeningProcessID}</p>
|
||||||
<div class="progress">
|
</li>
|
||||||
<div class="circle"></div>
|
`
|
||||||
<div class="line"></div>
|
})()
|
||||||
</div>
|
} else if (type === 'loanClosing') {
|
||||||
<div class="details">
|
return Component(() => {
|
||||||
${hasAgreedToLend ? html`
|
const [isRequestingCollateral, setIsRequestingCollateral] = useState(false)
|
||||||
<h4>Started lending process</h4>
|
let failedToUnlockCollateralAfter24Hours = false
|
||||||
<time>${getFormattedTime(loanResponseTime)}</time>
|
if (isCoborrower && hasRequestedCollateralLock && !hasRefundedCollateral) {
|
||||||
`: html`
|
const timeDiff = Date.now() - collateralLockAckTime
|
||||||
<h4>Waiting for a lender</h4>
|
if (timeDiff > 86400000) {
|
||||||
<p>Your loan request has been posted to the marketplace. Waiting for a lender to start lending process.</p>
|
failedToUnlockCollateralAfter24Hours = true
|
||||||
`}
|
}
|
||||||
</div>
|
}
|
||||||
</li>
|
async function requestCollateralUnlock() {
|
||||||
<li class=${`${hasLockedCollateral ? 'done' : ''}`}>
|
const confirmation = await getConfirmation('Request collateral refund?', { message: `You are about to request for collateral refund. Continue?`, confirmText: 'Request', cancelText: 'Cancel' })
|
||||||
<div class="progress">
|
if (!confirmation)
|
||||||
<div class="circle"></div>
|
return;
|
||||||
<div class="line"></div>
|
setIsRequestingCollateral(true)
|
||||||
</div>
|
try {
|
||||||
<div class="details">
|
await btcMortgage.requestUnlockCollateral(loanID, closingTxID, await floDapps.user.private, failedToUnlockCollateralAfter24Hours)
|
||||||
${hasLockedCollateral ? html`
|
notify('Collateral refund requested successfully', 'success')
|
||||||
<h4>Collateral locked</h4>
|
} catch (err) {
|
||||||
${hasLockedCollateralForMoreThan24Hours ? html`
|
notify(err, 'error')
|
||||||
${hasRequestedCollateralRefund ? html`
|
} finally {
|
||||||
<p>Collateral refund requested. It'll be refunded within 24 hrs.</p>
|
setIsRequestingCollateral(false)
|
||||||
`: html`
|
}
|
||||||
<p>
|
}
|
||||||
Loan has not been issued even after 24 hours of collateral lock.
|
return html`
|
||||||
You can request for collateral refund.
|
<li class="loan-process">
|
||||||
</p>
|
${isBorrower || coborrower ? html`
|
||||||
<button class="button button--primary margin-right-auto" disabled=${requestCollateralRefund} onclick=${requestCollateralRefund}>
|
<h4 class="loan-process__type">Repaying loan</h4>
|
||||||
${requestingCollateralRefund ? html`
|
`: html`
|
||||||
|
<h4 class="loan-process__type">Borrower repaying</h4>
|
||||||
|
`}
|
||||||
|
<ul>
|
||||||
|
<li class="done">
|
||||||
|
<div class="progress">
|
||||||
|
<div class="circle"></div>
|
||||||
|
<div class="line"></div>
|
||||||
|
</div>
|
||||||
|
<div class="details">
|
||||||
|
<h4>Due amount paid</h4>
|
||||||
|
<time>${getFormattedTime(loanClosedAckTime)}</time>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
<li class=${hasRequestedCollateralUnlock ? 'done' : ''}>
|
||||||
|
<div class="progress">
|
||||||
|
<div class="circle"></div>
|
||||||
|
<div class="line"></div>
|
||||||
|
</div>
|
||||||
|
<div class="details">
|
||||||
|
${hasRequestedCollateralLock ? html`
|
||||||
|
${failedToUnlockCollateralAfter24Hours ? html`
|
||||||
|
<h4>Lender hasn't unlocked collateral</h4>
|
||||||
|
<p>Collateral failed to unlock even after 24 hours of loan closing. Please request to banker below.</p>
|
||||||
|
<button class="button button--primary margin-right-auto" disabled=${isRequestingCollateral} onclick=${requestCollateralUnlock}>
|
||||||
|
${isRequestingCollateral ? html`
|
||||||
Requesting collateral refund
|
Requesting collateral refund
|
||||||
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
||||||
`: html`
|
`: html`
|
||||||
Request collateral refund
|
Request collateral refund
|
||||||
`}
|
`}
|
||||||
</button>
|
</button>
|
||||||
|
`: html`
|
||||||
|
<h4>Collateral unlock requested</h4>
|
||||||
|
<time>${getFormattedTime(unlockCollateralRequestTime)}</time>
|
||||||
`}
|
`}
|
||||||
`: html`
|
`: html`
|
||||||
<time>${getFormattedTime(collateralLockAckTime)}</time>
|
${isCoborrower ? html`
|
||||||
|
<h4>Request collateral refund</h4>
|
||||||
|
<button class="button button--primary margin-right-auto" disabled=${isRequestingCollateral} onclick=${requestCollateralUnlock}>
|
||||||
|
${isRequestingCollateral ? html`
|
||||||
|
Requesting collateral refund
|
||||||
|
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
||||||
|
`: html`
|
||||||
|
Request collateral refund
|
||||||
|
`}
|
||||||
|
</button>
|
||||||
|
`: html`
|
||||||
|
<h4>Waiting for Co-borrower to request collateral refund</h4>
|
||||||
|
<p>Collateral refund can be requested only by Co-borrower</p>
|
||||||
|
`}
|
||||||
`}
|
`}
|
||||||
`: html`
|
</div>
|
||||||
<h4>Waiting for collateral to be locked</h4>
|
</li>
|
||||||
<p>${isCoborrower ? 'You need' : 'Loan co-borrower needs'} to lock collateral before loan can be issued.</p>
|
<li class=${hasRefundedCollateral ? 'done' : ''}>
|
||||||
${hasAgreedToLend && (isBorrower || isCoborrower) ? html`
|
<div class="progress">
|
||||||
${floCrypto.isSameAddr(borrower, coborrower) ? html`
|
<div class="circle"></div>
|
||||||
<button class="button button--primary margin-right-auto" disabled=${lockingCollateral} onclick=${lockCollateral}>
|
</div>
|
||||||
${lockingCollateral ? html`
|
<div class="details">
|
||||||
Locking collateral
|
${hasRefundedCollateral ? html`
|
||||||
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
<h4>Collateral refunded. Loan closed.</h4>
|
||||||
`: html`
|
<time>${getFormattedTime(unlockCollateralAckTime)}</time>
|
||||||
Lock collateral
|
|
||||||
`}
|
|
||||||
</button>
|
|
||||||
`: html`
|
|
||||||
<button class="button button--primary margin-right-auto" onclick=${requestCollateralLock}>Request collateral lock</button>
|
|
||||||
`}
|
|
||||||
`: ''}
|
|
||||||
`}
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li class=${`${hasIssuedLoan ? 'done' : ''}`}>
|
|
||||||
<div class="progress">
|
|
||||||
<div class="circle"></div>
|
|
||||||
</div>
|
|
||||||
<div class="details">
|
|
||||||
${hasIssuedLoan ? html`
|
|
||||||
<h4>Loan issued</h4>
|
|
||||||
<time>${getFormattedTime(loanIssuedTime)}</time>
|
|
||||||
`: html`
|
|
||||||
${hasLockedCollateral && isLender ? html`
|
|
||||||
${failSafe ? html`
|
|
||||||
<h4>Loan failed to issue</h4>
|
|
||||||
<button class="button button--primary margin-right-auto" disabled=${retringFailSafe} onclick=${retryFailSafe}>
|
|
||||||
${retringFailSafe ? html`
|
|
||||||
Retrying
|
|
||||||
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
|
||||||
`: html`
|
|
||||||
Retry
|
|
||||||
`}
|
|
||||||
</button>
|
|
||||||
`: html`
|
|
||||||
<h4>Loan is ready to be issued</h4>
|
|
||||||
<button class="button button--primary margin-right-auto" disabled=${isIssuingLoan} onclick=${issueLoan}>
|
|
||||||
${isIssuingLoan ? html`
|
|
||||||
Issuing loan
|
|
||||||
<sm-spinner class="margin-left-0-5"></sm-spinner>
|
|
||||||
`: html`
|
|
||||||
Issue loan
|
|
||||||
`}
|
|
||||||
</button>
|
|
||||||
`}
|
|
||||||
`: html`
|
`: html`
|
||||||
<h4>Waiting for loan to be issued</h4>
|
<h4>Waiting for collateral to be refunded</h4>
|
||||||
|
<p>Collateral will be refunded within 24 hours of request.</p>
|
||||||
`}
|
`}
|
||||||
`}
|
</div>
|
||||||
</div>
|
</li>
|
||||||
</li>
|
</ul>
|
||||||
</ul>
|
</li>
|
||||||
<p class="margin-left-auto">Request ID: #${loanOpeningProcessID}</p>
|
`
|
||||||
</li>
|
})()
|
||||||
`
|
} else {
|
||||||
})()
|
console.log('Unknown loan process type', type)
|
||||||
|
return html``
|
||||||
|
}
|
||||||
},
|
},
|
||||||
loan(loanID) {
|
loan(loanID) {
|
||||||
const {
|
const {
|
||||||
@ -949,20 +1055,26 @@
|
|||||||
const amountDue = btcMortgage.util.calcDueAmount(loan_amount, policy_id, open_time)
|
const amountDue = btcMortgage.util.calcDueAmount(loan_amount, policy_id, open_time)
|
||||||
const [isRepayingLoan, setIsRepayingLoan] = useState(false)
|
const [isRepayingLoan, setIsRepayingLoan] = useState(false)
|
||||||
async function initLoanRepayment() {
|
async function initLoanRepayment() {
|
||||||
const confirmation = await getConfirmation('Repay loan?', { message: `You are about to repay ${formatAmount(amountDue)} as loan. Continue?`, confirmText: 'Repay', cancelText: 'Cancel' })
|
const confirmation = await getConfirmation('Repay loan?', { message: `You are about to repay ${formatAmount(amountDue, 'usd')} as loan. Continue?`, confirmText: 'Repay', cancelText: 'Cancel' })
|
||||||
if (!confirmation)
|
if (!confirmation)
|
||||||
return;
|
return;
|
||||||
setIsRepayingLoan(true)
|
setIsRepayingLoan(true)
|
||||||
try {
|
try {
|
||||||
await btcMortgage.repayLoan(loan_id, await floDapps.user.private)
|
const { message: { closing_txid } } = await btcMortgage.repayLoan(loan_id, await floDapps.user.private)
|
||||||
notify('Loan repaid successfully', 'success')
|
notify('Loan repaid successfully', 'success')
|
||||||
|
if (isCoborrower) {
|
||||||
|
await btcMortgage.requestUnlockCollateral(loan_id, closing_txid, await floDapps.user.private)
|
||||||
|
notify('Collateral refund requested', 'success')
|
||||||
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
notify(err, 'error')
|
notify(err, 'error')
|
||||||
} finally {
|
} finally {
|
||||||
setIsRepayingLoan(false)
|
setIsRepayingLoan(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id)
|
||||||
|
const isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id)
|
||||||
|
const isLender = floCrypto.isSameAddr(lender, floDapps.user.id)
|
||||||
return html`
|
return html`
|
||||||
<li class="loan">
|
<li class="loan">
|
||||||
<sm-copy value=${loan_id} clip-text>
|
<sm-copy value=${loan_id} clip-text>
|
||||||
@ -990,6 +1102,10 @@
|
|||||||
<b>${getFormattedTime(open_time)}</b>
|
<b>${getFormattedTime(open_time)}</b>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="grid gap-0-3">
|
||||||
|
<p>Interest ${isLender ? 'earned' : 'accrued'}</p>
|
||||||
|
<b style=${`color: ${isLender ? 'var(--green)' : 'var(--danger-color)'}`}>${formatAmount(amountDue - loan_amount, 'usd')}</b>
|
||||||
|
</div>
|
||||||
<details>
|
<details>
|
||||||
<summary>
|
<summary>
|
||||||
Borrower details
|
Borrower details
|
||||||
@ -1298,14 +1414,14 @@
|
|||||||
} else {
|
} else {
|
||||||
// user homepage
|
// user homepage
|
||||||
const inbox = Component(() => {
|
const inbox = Component(() => {
|
||||||
const [inProcessLoanOpeningRequests, inProcessLoanClosingRequests] = groupLoanProcess()
|
const loansInProcess = groupLoanProcess()
|
||||||
const { borrowed, coBorrowed, lent } = groupLoans()
|
const { borrowed, coBorrowed, lent } = groupLoans()
|
||||||
const [view, setView] = useState(section)
|
const [view, setView] = useState(section)
|
||||||
let loanRequests = [];
|
let loanRequests = [];
|
||||||
for (const key in floGlobals.loanRequests) {
|
for (const key in floGlobals.loanRequests) {
|
||||||
const { message: { borrower, coborrower, loan_opening_process_id } } = floGlobals.loanRequests[key]
|
const { message: { borrower, coborrower, loan_opening_process_id } } = floGlobals.loanRequests[key]
|
||||||
if (!loan_opening_process_id) continue // TODO: remove this check after all requests are updated
|
if (!loan_opening_process_id) continue // TODO: remove this check after all requests are updated
|
||||||
if (floGlobals.inProcessLoanOpeningRequests.has(loan_opening_process_id)) continue // if loan request is in process, don't show loan request
|
if (floGlobals.loansInProcess.has(loan_opening_process_id)) continue // if loan request is in process, don't show loan request
|
||||||
loanRequests.push(key)
|
loanRequests.push(key)
|
||||||
}
|
}
|
||||||
function handleChange(e) {
|
function handleChange(e) {
|
||||||
@ -1326,7 +1442,7 @@
|
|||||||
<sm-chips onchange=${handleChange}>
|
<sm-chips onchange=${handleChange}>
|
||||||
<sm-chip value="in-process" selected=${view === 'in-process'}>
|
<sm-chip value="in-process" selected=${view === 'in-process'}>
|
||||||
In process
|
In process
|
||||||
${inProcessLoanOpeningRequests.length ? html`<span class="badge">${inProcessLoanOpeningRequests.length}</span>` : ''}
|
${loansInProcess.length ? html`<span class="badge">${loansInProcess.length}</span>` : ''}
|
||||||
</sm-chip>
|
</sm-chip>
|
||||||
<sm-chip value="my-loans" selected=${view === 'my-loans'}>
|
<sm-chip value="my-loans" selected=${view === 'my-loans'}>
|
||||||
My loans
|
My loans
|
||||||
@ -1369,9 +1485,9 @@
|
|||||||
`}
|
`}
|
||||||
`: ''}
|
`: ''}
|
||||||
${view === 'in-process' ? html`
|
${view === 'in-process' ? html`
|
||||||
${inProcessLoanOpeningRequests.length ? html`
|
${loansInProcess.length ? html`
|
||||||
<ul class="grid gap-1">
|
<ul class="grid gap-1">
|
||||||
${inProcessLoanOpeningRequests.map(loan => render.loanProcess(loan))}
|
${loansInProcess.map(loan => render.loanProcess(loan))}
|
||||||
</ul>
|
</ul>
|
||||||
<a href="#/apply-loan" class="button button--primary margin-right-auto">Apply for a new loan</a>
|
<a href="#/apply-loan" class="button button--primary margin-right-auto">Apply for a new loan</a>
|
||||||
`: html`
|
`: html`
|
||||||
@ -1440,7 +1556,6 @@
|
|||||||
...floGlobals.myOutbox
|
...floGlobals.myOutbox
|
||||||
}
|
}
|
||||||
const loansInProcess = {}
|
const loansInProcess = {}
|
||||||
const loanClosingInProcess = {}
|
|
||||||
for (const key in allMessages) {
|
for (const key in allMessages) {
|
||||||
const {
|
const {
|
||||||
message: {
|
message: {
|
||||||
@ -1459,6 +1574,7 @@
|
|||||||
hasProvidedCollateral: false,
|
hasProvidedCollateral: false,
|
||||||
hasAgreedToLend: false,
|
hasAgreedToLend: false,
|
||||||
hasLockedCollateral: false,
|
hasLockedCollateral: false,
|
||||||
|
type: 'loanOpening'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (borrower) {
|
if (borrower) {
|
||||||
@ -1518,42 +1634,50 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else if (closing_txid) {
|
} else if (closing_txid) {
|
||||||
if (!loanClosingInProcess[closing_txid])
|
if (!loansInProcess[closing_txid])
|
||||||
loanClosingInProcess[closing_txid] = {
|
loansInProcess[closing_txid] = {
|
||||||
closingTxID: closing_txid,
|
closingTxID: closing_txid,
|
||||||
hasRequestedCollateralRefund: false,
|
hasRequestedCollateralRefund: false,
|
||||||
hasRefundedCollateral: false,
|
hasRefundedCollateral: false,
|
||||||
hasPaidLoan: false,
|
hasPaidLoan: false,
|
||||||
|
type: 'loanClosing'
|
||||||
}
|
}
|
||||||
|
const { borrower, coborrower, lender } = btcMortgage.loans[loan_id]
|
||||||
|
if (borrower) {
|
||||||
|
loansInProcess[closing_txid].borrower = borrower
|
||||||
|
loansInProcess[closing_txid].isBorrower = floCrypto.isSameAddr(borrower, floDapps.user.id)
|
||||||
|
}
|
||||||
|
if (coborrower) {
|
||||||
|
loansInProcess[closing_txid].coborrower = coborrower
|
||||||
|
loansInProcess[closing_txid].isCoborrower = floCrypto.isSameAddr(coborrower, floDapps.user.id)
|
||||||
|
}
|
||||||
|
if (lender) {
|
||||||
|
loansInProcess[closing_txid].lender = lender
|
||||||
|
loansInProcess[closing_txid].isLender = floCrypto.isSameAddr(lender, floDapps.user.id)
|
||||||
|
}
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'type_loan_closed_ack':
|
case 'type_loan_closed_ack':
|
||||||
loanClosingInProcess[closing_txid].loanClosedAckTime = time
|
loansInProcess[closing_txid].loanClosedAckTime = time
|
||||||
loanClosingInProcess[closing_txid].loanClosedAckID = key
|
loansInProcess[closing_txid].loanClosedAckID = key
|
||||||
loanClosingInProcess[closing_txid].hasPaidLoan = true
|
loansInProcess[closing_txid].hasPaidLoan = true
|
||||||
break;
|
break;
|
||||||
case 'type_unlock_collateral_request':
|
case 'type_unlock_collateral_request':
|
||||||
loanClosingInProcess[closing_txid].unlockCollateralRequestTime = time
|
loansInProcess[closing_txid].unlockCollateralRequestTime = time
|
||||||
loanClosingInProcess[closing_txid].unlockCollateralRequestID = key
|
loansInProcess[closing_txid].unlockCollateralRequestID = key
|
||||||
loanClosingInProcess[closing_txid].hasRequestedCollateralUnlock = true
|
loansInProcess[closing_txid].hasRequestedCollateralUnlock = true
|
||||||
break;
|
break;
|
||||||
case 'type_unlock_collateral_ack':
|
case 'type_unlock_collateral_ack':
|
||||||
loanClosingInProcess[closing_txid].unlockCollateralAckTime = time
|
loansInProcess[closing_txid].unlockCollateralAckTime = time
|
||||||
loanClosingInProcess[closing_txid].unlockCollateralAckID = key
|
loansInProcess[closing_txid].unlockCollateralAckID = key
|
||||||
loanClosingInProcess[closing_txid].hasRefundedCollateral = true
|
loansInProcess[closing_txid].hasRefundedCollateral = true
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// sort by time
|
// sort by time
|
||||||
return [
|
return Object.values(loansInProcess).sort((a, b) => {
|
||||||
Object.values(loansInProcess).sort((a, b) => {
|
return (b.initiationTime || b.loanResponseTime || b.loanClosedAckTime) - (a.initiationTime || a.loanResponseTime || a.loanClosedAckTime)
|
||||||
return (b.initiationTime || b.loanResponseTime) - (a.initiationTime || a.loanResponseTime)
|
})
|
||||||
}),
|
|
||||||
Object.values(loanClosingInProcess).sort((a, b) => {
|
|
||||||
return b.loanClosedAckTime - a.loanClosedAckTime
|
|
||||||
})
|
|
||||||
]
|
|
||||||
// .reduce((acc, loan) => { // group by borrower, coborrower and lender
|
// .reduce((acc, loan) => { // group by borrower, coborrower and lender
|
||||||
// if (loan.isBorrower)
|
// if (loan.isBorrower)
|
||||||
// acc.borrowing.push(loan)
|
// acc.borrowing.push(loan)
|
||||||
@ -1804,11 +1928,11 @@
|
|||||||
}).catch(error => console.error(error))
|
}).catch(error => console.error(error))
|
||||||
}
|
}
|
||||||
function parseInProcessRequests(requests) {
|
function parseInProcessRequests(requests) {
|
||||||
if (!floGlobals.inProcessLoanOpeningRequests)
|
if (!floGlobals.loansInProcess)
|
||||||
floGlobals.inProcessLoanOpeningRequests = new Set()
|
floGlobals.loansInProcess = new Set()
|
||||||
for (const key in requests) {
|
for (const key in requests) {
|
||||||
const { message: { loan_opening_process_id } } = requests[key]
|
const { message: { loan_opening_process_id } } = requests[key]
|
||||||
floGlobals.inProcessLoanOpeningRequests.add(loan_opening_process_id)
|
floGlobals.loansInProcess.add(loan_opening_process_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function getAllInvolvedAddresses() { // get all addresses involved in loan process (except my address)
|
function getAllInvolvedAddresses() { // get all addresses involved in loan process (except my address)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user