forked from Nova-reward/Nova-Rewards
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathTransactionModal.js
More file actions
145 lines (134 loc) · 5.2 KB
/
Copy pathTransactionModal.js
File metadata and controls
145 lines (134 loc) · 5.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
'use client';
import { useEffect, useRef } from 'react';
import { useFocusTrap } from '../lib/focusTrap';
import { formatTokenAmount } from '../lib/formatting';
/**
* TransactionModal — reusable modal for the full on-chain transaction flow.
*
* States: 'confirm' | 'loading' | 'success' | 'error'
*
* Props:
* state - current modal state
* action - human label e.g. "Redeem", "Stake", "Transfer"
* amount - token amount string
* asset - asset code, default "NOVA"
* feeEstimate - fee string e.g. "0.00001 XLM"
* walletAddress - truncated or full address shown in confirm step
* irreversible - boolean, shows warning banner when true
* txHash - Stellar tx hash (success state)
* errorMessage - error text (error state)
* onConfirm - called when user clicks Confirm
* onClose - called when modal should close
* onRetry - called when user clicks Retry (error state)
*
* Issue #616
*/
export default function TransactionModal({
state = 'confirm',
action = 'Transaction',
amount,
asset = 'NOVA',
feeEstimate,
walletAddress,
irreversible = false,
txHash,
errorMessage,
onConfirm,
onClose,
onRetry,
}) {
const overlayRef = useRef(null);
useFocusTrap(overlayRef, state !== null);
// Close on Escape
useEffect(() => {
const handler = (e) => { if (e.key === 'Escape' && state !== 'loading') onClose?.(); };
document.addEventListener('keydown', handler);
return () => document.removeEventListener('keydown', handler);
}, [state, onClose]);
return (
<div
ref={overlayRef}
role="dialog"
aria-modal="true"
aria-labelledby="tx-modal-title"
className="tx-modal-overlay"
onClick={(e) => { if (e.target === e.currentTarget && state !== 'loading') onClose?.(); }}
>
<div className="tx-modal-panel">
{state === 'confirm' && <ConfirmStep {...{ action, amount, asset, feeEstimate, walletAddress, irreversible, onConfirm, onClose }} />}
{state === 'loading' && <LoadingStep action={action} />}
{state === 'success' && <SuccessStep {...{ txHash, onClose }} />}
{state === 'error' && <ErrorStep {...{ errorMessage, onRetry, onClose }} />}
</div>
</div>
);
}
/* ── Sub-steps ─────────────────────────────────────────────────────────── */
function ConfirmStep({ action, amount, asset, feeEstimate, walletAddress, irreversible, onConfirm, onClose }) {
const formattedAmount = asset === 'NOVA' ? formatTokenAmount(amount) : amount;
return (
<>
<h2 id="tx-modal-title" className="tx-modal-title">Confirm {action}</h2>
<dl className="tx-modal-details">
<div><dt>Action</dt><dd>{action}</dd></div>
{amount && <div><dt>Amount</dt><dd>{formattedAmount} {asset}</dd></div>}
{feeEstimate && <div><dt>Network fee</dt><dd>{feeEstimate}</dd></div>}
{walletAddress && <div><dt>Wallet</dt><dd className="tx-modal-address">{walletAddress}</dd></div>}
</dl>
{irreversible && (
<p role="alert" className="tx-modal-warning">
⚠️ This action is irreversible and cannot be undone.
</p>
)}
<div className="tx-modal-actions">
<button className="btn btn-secondary" onClick={onClose}>Cancel</button>
<button className="btn btn-primary" onClick={onConfirm}>Confirm {action}</button>
</div>
</>
);
}
function LoadingStep({ action }) {
return (
<div className="tx-modal-center" role="status" aria-live="polite">
<span className="tx-modal-spinner" aria-hidden="true" />
<p id="tx-modal-title">Signing & submitting {action}…</p>
<p className="tx-modal-sub">Please approve in your wallet.</p>
</div>
);
}
function SuccessStep({ txHash, onClose }) {
const explorerUrl = `https://stellar.expert/explorer/testnet/tx/${txHash}`;
return (
<>
<div className="tx-modal-center">
<span className="tx-modal-icon tx-modal-icon--success" aria-hidden="true">✓</span>
<h2 id="tx-modal-title" className="tx-modal-title">Transaction Confirmed</h2>
{txHash && (
<p className="tx-modal-sub">
<a href={explorerUrl} target="_blank" rel="noopener noreferrer" className="tx-modal-link">
View on Stellar Explorer ↗
</a>
</p>
)}
</div>
<div className="tx-modal-actions tx-modal-actions--center">
<button className="btn btn-primary" onClick={onClose}>Done</button>
</div>
</>
);
}
function ErrorStep({ errorMessage, onRetry, onClose }) {
return (
<>
<div className="tx-modal-center">
<span className="tx-modal-icon tx-modal-icon--error" aria-hidden="true">✕</span>
<h2 id="tx-modal-title" className="tx-modal-title">Transaction Failed</h2>
{errorMessage && <p className="tx-modal-sub tx-modal-error-msg">{errorMessage}</p>}
</div>
<div className="tx-modal-actions">
<button className="btn btn-secondary" onClick={onClose}>Close</button>
{onRetry && <button className="btn btn-primary" onClick={onRetry}>Retry</button>}
</div>
</>
);
}