@nockchain/rose 0.1.4-nightly.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/artifacts.yml +33 -0
- package/.github/workflows/ci.yml +68 -0
- package/.github/workflows/publish-sdk.yml +35 -0
- package/.nvmrc +1 -0
- package/.prettierignore +5 -0
- package/.prettierrc +8 -0
- package/LICENSE +22 -0
- package/README.md +117 -0
- package/extension/background/index.ts +1500 -0
- package/extension/content/index.ts +59 -0
- package/extension/icons/rose.svg +27 -0
- package/extension/icons/rose128.png +0 -0
- package/extension/icons/rose16.png +0 -0
- package/extension/icons/rose256.png +0 -0
- package/extension/icons/rose32.png +0 -0
- package/extension/icons/rose48.png +0 -0
- package/extension/icons/rose512.png +0 -0
- package/extension/inpage/index.ts +86 -0
- package/extension/manifest.json +48 -0
- package/extension/popup/Popup.tsx +94 -0
- package/extension/popup/Router.tsx +121 -0
- package/extension/popup/assets/arrow-down-icon.svg +3 -0
- package/extension/popup/assets/arrow-left-icon.svg +3 -0
- package/extension/popup/assets/arrow-right-icon.svg +3 -0
- package/extension/popup/assets/arrow-up-icon.svg +3 -0
- package/extension/popup/assets/arrow-up-right-icon.svg +3 -0
- package/extension/popup/assets/checkmark-icon.svg +3 -0
- package/extension/popup/assets/checkmark-pencil-icon.svg +3 -0
- package/extension/popup/assets/checkmark-success-icon.svg +3 -0
- package/extension/popup/assets/clock-icon.svg +3 -0
- package/extension/popup/assets/close-x-icon.svg +3 -0
- package/extension/popup/assets/copy-icon.svg +6 -0
- package/extension/popup/assets/explorer-icon.svg +3 -0
- package/extension/popup/assets/eye-off-icon.svg +3 -0
- package/extension/popup/assets/eye-open-icon.svg +4 -0
- package/extension/popup/assets/feedback-icon.svg +3 -0
- package/extension/popup/assets/green-status-dot.svg +3 -0
- package/extension/popup/assets/info-icon.svg +3 -0
- package/extension/popup/assets/iris-logo-40.svg +27 -0
- package/extension/popup/assets/iris-logo-96.svg +27 -0
- package/extension/popup/assets/iris-logo-blue.svg +27 -0
- package/extension/popup/assets/iris-logo-no-eye.svg +27 -0
- package/extension/popup/assets/iris-logo-orange.svg +27 -0
- package/extension/popup/assets/iris-logo.svg +27 -0
- package/extension/popup/assets/key-icon.svg +3 -0
- package/extension/popup/assets/lock-icon-yellow.svg +3 -0
- package/extension/popup/assets/lock-icon.svg +3 -0
- package/extension/popup/assets/pencil-edit-icon.svg +3 -0
- package/extension/popup/assets/permissions-icon.svg +3 -0
- package/extension/popup/assets/receipt-icon.svg +5 -0
- package/extension/popup/assets/refresh-icon.svg +3 -0
- package/extension/popup/assets/settings-gear-icon.svg +8 -0
- package/extension/popup/assets/settings-icon.svg +3 -0
- package/extension/popup/assets/theme-icon.svg +3 -0
- package/extension/popup/assets/trash-bin-icon.svg +3 -0
- package/extension/popup/assets/trend-down-arrow.svg +5 -0
- package/extension/popup/assets/trend-up-arrow.svg +5 -0
- package/extension/popup/assets/user-account-icon.svg +3 -0
- package/extension/popup/assets/vector-bottom-left.svg +9 -0
- package/extension/popup/assets/vector-left.svg +9 -0
- package/extension/popup/assets/vector-right.svg +9 -0
- package/extension/popup/assets/vector-top-right-rotated.svg +8 -0
- package/extension/popup/assets/vector-top-right.svg +9 -0
- package/extension/popup/assets/wallet-dropdown-arrow.svg +5 -0
- package/extension/popup/assets/wallet-icon-style-1.svg +6 -0
- package/extension/popup/assets/wallet-icon-style-10.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-11.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-12.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-13.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-14.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-15.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-2.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-3.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-4.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-5.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-6.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-7.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-8.svg +8 -0
- package/extension/popup/assets/wallet-icon-style-9.svg +8 -0
- package/extension/popup/components/AccountIcon.tsx +78 -0
- package/extension/popup/components/AccountSelector.tsx +246 -0
- package/extension/popup/components/Alert.tsx +48 -0
- package/extension/popup/components/ConfirmModal.tsx +81 -0
- package/extension/popup/components/PasswordInput.tsx +49 -0
- package/extension/popup/components/ScreenContainer.tsx +17 -0
- package/extension/popup/components/SiteIcon.tsx +60 -0
- package/extension/popup/components/ThemeToggle.tsx +44 -0
- package/extension/popup/components/icons/ArrowDownLeftIcon.tsx +20 -0
- package/extension/popup/components/icons/ArrowUpRightIcon.tsx +20 -0
- package/extension/popup/components/icons/CheckIcon.tsx +20 -0
- package/extension/popup/components/icons/ChevronDownIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronLeftIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronRightIcon.tsx +15 -0
- package/extension/popup/components/icons/ChevronUpIcon.tsx +15 -0
- package/extension/popup/components/icons/CloseIcon.tsx +26 -0
- package/extension/popup/components/icons/CopyIcon.tsx +20 -0
- package/extension/popup/components/icons/EditIcon.tsx +20 -0
- package/extension/popup/components/icons/EyeIcon.tsx +13 -0
- package/extension/popup/components/icons/EyeOffIcon.tsx +13 -0
- package/extension/popup/components/icons/InfoIcon.tsx +20 -0
- package/extension/popup/components/icons/LockIcon.tsx +20 -0
- package/extension/popup/components/icons/PlusIcon.tsx +15 -0
- package/extension/popup/components/icons/ReceiveArrowIcon.tsx +14 -0
- package/extension/popup/components/icons/ReceiveCircleIcon.tsx +20 -0
- package/extension/popup/components/icons/SendPaperPlaneIcon.tsx +18 -0
- package/extension/popup/components/icons/SentArrowIcon.tsx +21 -0
- package/extension/popup/components/icons/SettingsIcon.tsx +26 -0
- package/extension/popup/components/icons/ShieldIcon.tsx +20 -0
- package/extension/popup/components/icons/UploadIcon.tsx +20 -0
- package/extension/popup/components/icons/WalletIcon.tsx +20 -0
- package/extension/popup/contexts/ThemeContext.tsx +105 -0
- package/extension/popup/hooks/useApprovalDetection.ts +128 -0
- package/extension/popup/hooks/useAutoFocus.ts +36 -0
- package/extension/popup/hooks/useAutoRejectOnClose.ts +25 -0
- package/extension/popup/hooks/useClickOutside.ts +33 -0
- package/extension/popup/hooks/useCopyToClipboard.ts +33 -0
- package/extension/popup/hooks/useFavicon.ts +64 -0
- package/extension/popup/hooks/useNumericInput.ts +93 -0
- package/extension/popup/index.html +13 -0
- package/extension/popup/index.tsx +24 -0
- package/extension/popup/screens/AboutScreen.tsx +118 -0
- package/extension/popup/screens/HomeScreen.tailwind.css +85 -0
- package/extension/popup/screens/HomeScreen.tsx +902 -0
- package/extension/popup/screens/KeySettingsPasswordScreen.tsx +164 -0
- package/extension/popup/screens/LockTimeScreen.tsx +155 -0
- package/extension/popup/screens/ReceiveScreen.tsx +149 -0
- package/extension/popup/screens/RecoveryPhraseScreen.tsx +183 -0
- package/extension/popup/screens/SendReviewScreen.tsx +308 -0
- package/extension/popup/screens/SendScreen.tsx +825 -0
- package/extension/popup/screens/SendSubmittedScreen.tsx +193 -0
- package/extension/popup/screens/SettingsScreen.tsx +116 -0
- package/extension/popup/screens/ThemeSettingsScreen.tsx +107 -0
- package/extension/popup/screens/TransactionDetailsScreen.tsx +346 -0
- package/extension/popup/screens/ViewSecretPhraseScreen.tsx +212 -0
- package/extension/popup/screens/WalletPermissionsScreen.tsx +123 -0
- package/extension/popup/screens/WalletSettingsScreen.tsx +381 -0
- package/extension/popup/screens/WalletStylingScreen.tsx +306 -0
- package/extension/popup/screens/approvals/ConnectApprovalScreen.tsx +136 -0
- package/extension/popup/screens/approvals/SignMessageScreen.tsx +140 -0
- package/extension/popup/screens/approvals/SignRawTxScreen.tsx +320 -0
- package/extension/popup/screens/approvals/TransactionApprovalScreen.tsx +167 -0
- package/extension/popup/screens/onboarding/BackupScreen.tsx +254 -0
- package/extension/popup/screens/onboarding/CreateScreen.tsx +273 -0
- package/extension/popup/screens/onboarding/ImportScreen.tsx +676 -0
- package/extension/popup/screens/onboarding/ImportScreenV0.tsx +678 -0
- package/extension/popup/screens/onboarding/ImportSuccessScreen.tsx +236 -0
- package/extension/popup/screens/onboarding/ResumeBackupScreen.tsx +166 -0
- package/extension/popup/screens/onboarding/StartScreen.tsx +142 -0
- package/extension/popup/screens/onboarding/SuccessScreen.tsx +193 -0
- package/extension/popup/screens/onboarding/VerifyScreen.tsx +220 -0
- package/extension/popup/screens/system/LockedScreen.tsx +288 -0
- package/extension/popup/screens/transactions/ReceiveScreen.tsx +84 -0
- package/extension/popup/screens/transactions/SentScreen.tsx +138 -0
- package/extension/popup/store.ts +482 -0
- package/extension/popup/styles.css +246 -0
- package/extension/popup/utils/format.ts +58 -0
- package/extension/popup/utils/formatWalletError.ts +36 -0
- package/extension/popup/utils/memo.ts +299 -0
- package/extension/popup/utils/messaging.ts +16 -0
- package/extension/shared/address-encoding.ts +69 -0
- package/extension/shared/balance-query.ts +123 -0
- package/extension/shared/constants.ts +386 -0
- package/extension/shared/currency.ts +128 -0
- package/extension/shared/first-name-derivation.ts +128 -0
- package/extension/shared/keyfile.ts +58 -0
- package/extension/shared/onboarding.ts +78 -0
- package/extension/shared/price-api.ts +79 -0
- package/extension/shared/rpc-client-browser.ts +315 -0
- package/extension/shared/transaction-builder.ts +443 -0
- package/extension/shared/types.ts +450 -0
- package/extension/shared/utxo-diff.ts +212 -0
- package/extension/shared/utxo-store.ts +548 -0
- package/extension/shared/utxo-sync.ts +343 -0
- package/extension/shared/validators.ts +26 -0
- package/extension/shared/vault.ts +1580 -0
- package/extension/shared/wallet-crypto.ts +77 -0
- package/extension/shared/wasm-utils.ts +76 -0
- package/extension/shared/webcrypto.ts +67 -0
- package/extension/types/wasm.d.ts +13 -0
- package/package.json +39 -0
- package/postcss.config.js +6 -0
- package/rose-extension-dist.zip +0 -0
- package/sdk/README.md +88 -0
- package/sdk/examples/app.ts +166 -0
- package/sdk/examples/index.html +51 -0
- package/sdk/examples/tsconfig.json +15 -0
- package/sdk/examples/tx-builder.html +532 -0
- package/sdk/examples/tx-builder.ts +1766 -0
- package/sdk/package-lock.json +424 -0
- package/sdk/package.json +68 -0
- package/sdk/src/constants.ts +28 -0
- package/sdk/src/errors.ts +74 -0
- package/sdk/src/hooks/index.ts +1 -0
- package/sdk/src/hooks/use-rose.ts +94 -0
- package/sdk/src/index.ts +12 -0
- package/sdk/src/provider.ts +396 -0
- package/sdk/src/transaction.ts +163 -0
- package/sdk/src/types/rose-wasm.d.ts +14 -0
- package/sdk/src/types.ts +97 -0
- package/sdk/src/wasm.ts +13 -0
- package/sdk/tsconfig.json +20 -0
- package/sdk/vite.config.examples.ts +32 -0
- package/tailwind.config.ts +38 -0
- package/tsconfig.json +20 -0
- package/vite.config.ts +60 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { useStore } from '../store';
|
|
3
|
+
import { ChevronLeftIcon } from '../components/icons/ChevronLeftIcon';
|
|
4
|
+
import { ChevronRightIcon } from '../components/icons/ChevronRightIcon';
|
|
5
|
+
import { CheckIcon } from '../components/icons/CheckIcon';
|
|
6
|
+
import RoseLogo40 from '../assets/iris-logo-40.svg';
|
|
7
|
+
import { truncateAddress, formatUTCTimestamp } from '../utils/format';
|
|
8
|
+
import { NOCK_TO_NICKS } from '../../shared/constants';
|
|
9
|
+
|
|
10
|
+
export function TransactionDetailsScreen() {
|
|
11
|
+
const {
|
|
12
|
+
navigate,
|
|
13
|
+
selectedTransaction,
|
|
14
|
+
wallet,
|
|
15
|
+
fetchWalletTransactions,
|
|
16
|
+
walletTransactions,
|
|
17
|
+
setSelectedTransaction,
|
|
18
|
+
} = useStore();
|
|
19
|
+
|
|
20
|
+
const [copiedTxId, setCopiedTxId] = useState(false);
|
|
21
|
+
|
|
22
|
+
// Fetch fresh transaction data on mount
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
fetchWalletTransactions();
|
|
25
|
+
}, []);
|
|
26
|
+
|
|
27
|
+
// Sync selectedTransaction with updates from walletTransactions
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
if (!selectedTransaction) return;
|
|
30
|
+
|
|
31
|
+
// Find the updated transaction by id
|
|
32
|
+
const updatedTx = walletTransactions.find(tx => tx.id === selectedTransaction.id);
|
|
33
|
+
if (updatedTx) {
|
|
34
|
+
// Update selectedTransaction with the latest data
|
|
35
|
+
setSelectedTransaction(updatedTx);
|
|
36
|
+
}
|
|
37
|
+
}, [walletTransactions, selectedTransaction?.id]);
|
|
38
|
+
|
|
39
|
+
// If no transaction selected, show error state
|
|
40
|
+
if (!selectedTransaction) {
|
|
41
|
+
return (
|
|
42
|
+
<div
|
|
43
|
+
className="w-[357px] h-[600px] flex items-center justify-center"
|
|
44
|
+
style={{ backgroundColor: 'var(--color-bg)' }}
|
|
45
|
+
>
|
|
46
|
+
<div className="text-center" style={{ color: 'var(--color-text-muted)' }}>
|
|
47
|
+
<p>No transaction selected</p>
|
|
48
|
+
<button
|
|
49
|
+
onClick={() => navigate('home')}
|
|
50
|
+
className="mt-4 px-4 py-2 rounded-lg"
|
|
51
|
+
style={{ backgroundColor: 'var(--color-primary)' }}
|
|
52
|
+
>
|
|
53
|
+
Go back
|
|
54
|
+
</button>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Extract data from selected transaction
|
|
61
|
+
const transactionType = selectedTransaction.direction === 'outgoing' ? 'sent' : 'received';
|
|
62
|
+
|
|
63
|
+
// Convert amount from nicks to NOCK
|
|
64
|
+
const amountNock = (selectedTransaction.amount || 0) / NOCK_TO_NICKS;
|
|
65
|
+
const feeNock = (selectedTransaction.fee || 0) / NOCK_TO_NICKS;
|
|
66
|
+
|
|
67
|
+
const amount = amountNock.toLocaleString('en-US', {
|
|
68
|
+
minimumFractionDigits: 2,
|
|
69
|
+
maximumFractionDigits: 2,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Only show USD value if we have historical price stored
|
|
73
|
+
const usdValue = selectedTransaction.priceUsdAtTime
|
|
74
|
+
? `$${(amountNock * selectedTransaction.priceUsdAtTime).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
|
75
|
+
: null;
|
|
76
|
+
|
|
77
|
+
// Determine status display
|
|
78
|
+
let statusText: string;
|
|
79
|
+
let statusColor: string;
|
|
80
|
+
|
|
81
|
+
switch (selectedTransaction.status) {
|
|
82
|
+
case 'confirmed':
|
|
83
|
+
statusText = 'Confirmed';
|
|
84
|
+
statusColor = 'var(--color-green)';
|
|
85
|
+
break;
|
|
86
|
+
case 'failed':
|
|
87
|
+
statusText = 'Failed';
|
|
88
|
+
statusColor = 'var(--color-red)';
|
|
89
|
+
break;
|
|
90
|
+
case 'expired':
|
|
91
|
+
statusText = 'Expired';
|
|
92
|
+
statusColor = 'var(--color-red)';
|
|
93
|
+
break;
|
|
94
|
+
case 'broadcasted_unconfirmed':
|
|
95
|
+
case 'broadcast_pending':
|
|
96
|
+
case 'created':
|
|
97
|
+
statusText = 'Pending';
|
|
98
|
+
statusColor = '#C88414';
|
|
99
|
+
break;
|
|
100
|
+
default:
|
|
101
|
+
statusText = 'Unknown';
|
|
102
|
+
statusColor = 'var(--color-text-muted)';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const currentAddress = wallet.currentAccount?.address || '';
|
|
106
|
+
const counterpartyAddress =
|
|
107
|
+
selectedTransaction.direction === 'outgoing'
|
|
108
|
+
? selectedTransaction.recipient
|
|
109
|
+
: selectedTransaction.sender;
|
|
110
|
+
|
|
111
|
+
const fromAddress =
|
|
112
|
+
selectedTransaction.direction === 'outgoing'
|
|
113
|
+
? truncateAddress(currentAddress)
|
|
114
|
+
: counterpartyAddress
|
|
115
|
+
? truncateAddress(counterpartyAddress)
|
|
116
|
+
: 'Unknown';
|
|
117
|
+
const toAddress =
|
|
118
|
+
selectedTransaction.direction === 'outgoing'
|
|
119
|
+
? truncateAddress(counterpartyAddress || '')
|
|
120
|
+
: truncateAddress(currentAddress);
|
|
121
|
+
|
|
122
|
+
// For incoming transactions, we don't have fee info
|
|
123
|
+
const networkFee =
|
|
124
|
+
selectedTransaction.direction === 'outgoing'
|
|
125
|
+
? `${feeNock.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} NOCK`
|
|
126
|
+
: '-';
|
|
127
|
+
const totalNock =
|
|
128
|
+
selectedTransaction.direction === 'outgoing' ? amountNock + feeNock : amountNock;
|
|
129
|
+
const total = `${totalNock.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} NOCK`;
|
|
130
|
+
|
|
131
|
+
// Only show total USD if we have historical price stored
|
|
132
|
+
const totalUsd = selectedTransaction.priceUsdAtTime
|
|
133
|
+
? `$${(totalNock * selectedTransaction.priceUsdAtTime).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`
|
|
134
|
+
: null;
|
|
135
|
+
const transactionId = selectedTransaction.txHash || selectedTransaction.id;
|
|
136
|
+
const transactionTimeUTC = formatUTCTimestamp(selectedTransaction.createdAt);
|
|
137
|
+
|
|
138
|
+
function handleBack() {
|
|
139
|
+
navigate('home');
|
|
140
|
+
}
|
|
141
|
+
function handleViewExplorer() {
|
|
142
|
+
// Open transaction on nockblocks.com (only if we have a txHash)
|
|
143
|
+
const txHash = selectedTransaction?.txHash;
|
|
144
|
+
if (txHash) {
|
|
145
|
+
window.open(`https://nockblocks.com/tx/${txHash}`, '_blank');
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async function handleCopyTransactionId() {
|
|
149
|
+
try {
|
|
150
|
+
await navigator.clipboard.writeText(transactionId);
|
|
151
|
+
setCopiedTxId(true);
|
|
152
|
+
setTimeout(() => setCopiedTxId(false), 2000);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
console.error('Failed to copy transaction ID:', err);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<div
|
|
160
|
+
className="w-[357px] h-[600px] flex flex-col"
|
|
161
|
+
style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
|
|
162
|
+
>
|
|
163
|
+
{/* Header */}
|
|
164
|
+
<header
|
|
165
|
+
className="flex items-center justify-between px-4 py-3 min-h-[64px]"
|
|
166
|
+
style={{ backgroundColor: 'var(--color-bg)' }}
|
|
167
|
+
>
|
|
168
|
+
<button
|
|
169
|
+
type="button"
|
|
170
|
+
onClick={handleBack}
|
|
171
|
+
className="w-8 h-8 flex items-center justify-center p-2 rounded-lg transition-opacity focus:outline-none focus-visible:ring-2"
|
|
172
|
+
style={{ color: 'var(--color-text-primary)' }}
|
|
173
|
+
onMouseEnter={e => (e.currentTarget.style.opacity = '0.7')}
|
|
174
|
+
onMouseLeave={e => (e.currentTarget.style.opacity = '1')}
|
|
175
|
+
aria-label="Back"
|
|
176
|
+
>
|
|
177
|
+
<ChevronLeftIcon className="w-5 h-5" />
|
|
178
|
+
</button>
|
|
179
|
+
<h1 className="m-0 text-base font-medium leading-[22px] tracking-[0.16px]">
|
|
180
|
+
{transactionType === 'sent' ? 'Sent' : 'Received'}
|
|
181
|
+
</h1>
|
|
182
|
+
<div className="w-8 h-8" />
|
|
183
|
+
</header>
|
|
184
|
+
|
|
185
|
+
{/* Content */}
|
|
186
|
+
<div
|
|
187
|
+
className="flex flex-col gap-2 h-[536px] overflow-y-auto"
|
|
188
|
+
style={{ backgroundColor: 'var(--color-bg)' }}
|
|
189
|
+
>
|
|
190
|
+
<div className="flex flex-col gap-8 px-4 py-2">
|
|
191
|
+
{/* Amount Section */}
|
|
192
|
+
<div className="flex flex-col items-center gap-3">
|
|
193
|
+
<img src={RoseLogo40} alt="Rose" className="w-10 h-10" />
|
|
194
|
+
<div className="flex flex-col items-center gap-0.5 text-center">
|
|
195
|
+
<h2
|
|
196
|
+
className="m-0 font-[Lora] text-[36px] font-semibold leading-10 tracking-[-0.72px]"
|
|
197
|
+
style={{ color: 'var(--color-text-primary)' }}
|
|
198
|
+
>
|
|
199
|
+
{transactionType === 'sent' && '-'}
|
|
200
|
+
{amount} <span style={{ color: 'var(--color-text-muted)' }}>NOCK</span>
|
|
201
|
+
</h2>
|
|
202
|
+
{usdValue && (
|
|
203
|
+
<p
|
|
204
|
+
className="m-0 text-[13px] font-medium leading-[18px] tracking-[0.26px]"
|
|
205
|
+
style={{ color: 'var(--color-text-muted)' }}
|
|
206
|
+
>
|
|
207
|
+
{usdValue}
|
|
208
|
+
</p>
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
211
|
+
</div>
|
|
212
|
+
|
|
213
|
+
{/* Transaction Details */}
|
|
214
|
+
<div className="flex flex-col gap-2">
|
|
215
|
+
{/* Status */}
|
|
216
|
+
<div
|
|
217
|
+
className="rounded-lg px-3 py-5"
|
|
218
|
+
style={{ backgroundColor: 'var(--color-surface-800)' }}
|
|
219
|
+
>
|
|
220
|
+
<div className="flex items-center justify-between text-sm font-medium leading-[18px] tracking-[0.14px]">
|
|
221
|
+
<div style={{ color: 'var(--color-text-primary)' }}>Status</div>
|
|
222
|
+
<div style={{ color: statusColor }}>
|
|
223
|
+
<span className="whitespace-nowrap">{statusText}</span>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
</div>
|
|
227
|
+
|
|
228
|
+
{/* Transaction Time */}
|
|
229
|
+
<div
|
|
230
|
+
className="rounded-lg px-3 py-5"
|
|
231
|
+
style={{ backgroundColor: 'var(--color-surface-800)' }}
|
|
232
|
+
>
|
|
233
|
+
<div className="flex items-center justify-between text-sm font-medium leading-[18px] tracking-[0.14px]">
|
|
234
|
+
<div style={{ color: 'var(--color-text-primary)' }}>Time</div>
|
|
235
|
+
<div
|
|
236
|
+
className="text-right text-[13px] leading-[18px] tracking-[0.26px]"
|
|
237
|
+
style={{ color: 'var(--color-text-muted)' }}
|
|
238
|
+
>
|
|
239
|
+
{transactionTimeUTC}
|
|
240
|
+
</div>
|
|
241
|
+
</div>
|
|
242
|
+
</div>
|
|
243
|
+
|
|
244
|
+
{/* From / To */}
|
|
245
|
+
<div className="rounded-lg p-3" style={{ backgroundColor: 'var(--color-surface-800)' }}>
|
|
246
|
+
<div className="flex items-center gap-2.5">
|
|
247
|
+
<div className="flex-1 flex flex-col gap-1 min-w-0">
|
|
248
|
+
<div className="text-sm font-medium leading-[18px] tracking-[0.14px]">From</div>
|
|
249
|
+
<div
|
|
250
|
+
className="text-[13px] leading-[18px] tracking-[0.26px] truncate"
|
|
251
|
+
style={{ color: 'var(--color-text-muted)' }}
|
|
252
|
+
>
|
|
253
|
+
{fromAddress}
|
|
254
|
+
</div>
|
|
255
|
+
</div>
|
|
256
|
+
<div className="p-1 shrink-0">
|
|
257
|
+
<ChevronRightIcon className="w-4 h-4" />
|
|
258
|
+
</div>
|
|
259
|
+
<div className="flex-1 flex flex-col gap-1 min-w-0">
|
|
260
|
+
<div className="text-sm font-medium leading-[18px] tracking-[0.14px]">To</div>
|
|
261
|
+
<div
|
|
262
|
+
className="text-[13px] leading-[18px] tracking-[0.26px] truncate"
|
|
263
|
+
style={{ color: 'var(--color-text-muted)' }}
|
|
264
|
+
>
|
|
265
|
+
{toAddress}
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
{/* Fee and Total */}
|
|
272
|
+
<div
|
|
273
|
+
className="rounded-lg px-3 py-3 flex flex-col gap-3"
|
|
274
|
+
style={{ backgroundColor: 'var(--color-surface-800)' }}
|
|
275
|
+
>
|
|
276
|
+
<div className="flex items-center justify-between text-sm font-medium leading-[18px] tracking-[0.14px]">
|
|
277
|
+
<div style={{ color: 'var(--color-text-primary)', opacity: 0.7 }}>Network fee</div>
|
|
278
|
+
<div className="whitespace-nowrap" style={{ color: 'var(--color-text-muted)' }}>
|
|
279
|
+
{networkFee}
|
|
280
|
+
</div>
|
|
281
|
+
</div>
|
|
282
|
+
<div className="h-px w-full" style={{ backgroundColor: 'var(--color-divider)' }} />
|
|
283
|
+
<div className="flex items-center justify-between text-sm font-medium leading-[18px] tracking-[0.14px]">
|
|
284
|
+
<div style={{ color: 'var(--color-text-primary)' }}>Total</div>
|
|
285
|
+
<div className="flex flex-col items-end gap-1 w-[75px]">
|
|
286
|
+
<div className="whitespace-nowrap" style={{ color: 'var(--color-text-primary)' }}>
|
|
287
|
+
{total}
|
|
288
|
+
</div>
|
|
289
|
+
{totalUsd && (
|
|
290
|
+
<div
|
|
291
|
+
className="text-[13px] leading-[18px] tracking-[0.26px] whitespace-nowrap"
|
|
292
|
+
style={{ color: 'var(--color-text-muted)' }}
|
|
293
|
+
>
|
|
294
|
+
{totalUsd}
|
|
295
|
+
</div>
|
|
296
|
+
)}
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
</div>
|
|
300
|
+
|
|
301
|
+
{/* Actions */}
|
|
302
|
+
<div className="flex gap-2">
|
|
303
|
+
<button
|
|
304
|
+
type="button"
|
|
305
|
+
onClick={handleViewExplorer}
|
|
306
|
+
disabled={!selectedTransaction.txHash}
|
|
307
|
+
className="flex-1 py-[7px] px-3 bg-transparent rounded-full text-sm font-medium leading-[18px] tracking-[0.14px] transition-colors focus:outline-none focus-visible:ring-2 whitespace-nowrap disabled:opacity-50"
|
|
308
|
+
style={{
|
|
309
|
+
border: '1px solid var(--color-surface-700)',
|
|
310
|
+
color: 'var(--color-text-primary)',
|
|
311
|
+
}}
|
|
312
|
+
onMouseEnter={e => {
|
|
313
|
+
if (!e.currentTarget.disabled) {
|
|
314
|
+
e.currentTarget.style.backgroundColor = 'var(--color-surface-800)';
|
|
315
|
+
}
|
|
316
|
+
}}
|
|
317
|
+
onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
|
|
318
|
+
>
|
|
319
|
+
View on explorer
|
|
320
|
+
</button>
|
|
321
|
+
<button
|
|
322
|
+
type="button"
|
|
323
|
+
onClick={handleCopyTransactionId}
|
|
324
|
+
disabled={copiedTxId}
|
|
325
|
+
className="flex-1 py-[7px] px-3 bg-transparent rounded-full text-sm font-medium leading-[18px] tracking-[0.14px] transition-colors focus:outline-none focus-visible:ring-2 whitespace-nowrap disabled:opacity-100 flex items-center justify-center gap-1.5"
|
|
326
|
+
style={{
|
|
327
|
+
border: '1px solid var(--color-surface-700)',
|
|
328
|
+
color: 'var(--color-text-primary)',
|
|
329
|
+
}}
|
|
330
|
+
onMouseEnter={e => {
|
|
331
|
+
if (!copiedTxId) {
|
|
332
|
+
e.currentTarget.style.backgroundColor = 'var(--color-surface-800)';
|
|
333
|
+
}
|
|
334
|
+
}}
|
|
335
|
+
onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
|
|
336
|
+
>
|
|
337
|
+
{copiedTxId && <CheckIcon className="w-3.5 h-3.5" />}
|
|
338
|
+
{copiedTxId ? 'Copied!' : 'Copy transaction ID'}
|
|
339
|
+
</button>
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
</div>
|
|
344
|
+
</div>
|
|
345
|
+
);
|
|
346
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useStore } from '../store';
|
|
3
|
+
import { ChevronLeftIcon } from '../components/icons/ChevronLeftIcon';
|
|
4
|
+
import { CheckIcon } from '../components/icons/CheckIcon';
|
|
5
|
+
import LockIcon from '../assets/lock-icon-yellow.svg';
|
|
6
|
+
import { exportKeyfile, downloadKeyfile } from '../../shared/keyfile';
|
|
7
|
+
import { Alert } from '../components/Alert';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* ViewSecretPhraseScreen - Display user's 24-word secret phrase
|
|
11
|
+
* Shows mnemonic secret phrase with security warnings and reveal functionality
|
|
12
|
+
*/
|
|
13
|
+
export function ViewSecretPhraseScreen() {
|
|
14
|
+
const { navigate, onboardingMnemonic, setOnboardingMnemonic } = useStore();
|
|
15
|
+
const [isRevealed, setIsRevealed] = useState(false);
|
|
16
|
+
const [error, setError] = useState('');
|
|
17
|
+
const [isExporting, setIsExporting] = useState(false);
|
|
18
|
+
const [copiedSeed, setCopiedSeed] = useState(false);
|
|
19
|
+
|
|
20
|
+
// Get secret phrase from temporary store (set by KeySettingsPasswordScreen)
|
|
21
|
+
const secretPhrase = onboardingMnemonic ? onboardingMnemonic.split(' ') : [];
|
|
22
|
+
|
|
23
|
+
function handleBack() {
|
|
24
|
+
// Clear mnemonic from memory when leaving screen
|
|
25
|
+
setOnboardingMnemonic(null);
|
|
26
|
+
navigate('settings');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function handleReveal() {
|
|
30
|
+
setIsRevealed(true);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function handleCopySecretPhrase() {
|
|
34
|
+
if (onboardingMnemonic) {
|
|
35
|
+
try {
|
|
36
|
+
await navigator.clipboard.writeText(onboardingMnemonic);
|
|
37
|
+
setCopiedSeed(true);
|
|
38
|
+
setTimeout(() => setCopiedSeed(false), 2000);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error('Failed to copy secret phrase:', err);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function handleDownloadKeyfile() {
|
|
46
|
+
if (!onboardingMnemonic) {
|
|
47
|
+
setError('No mnemonic available');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
setIsExporting(true);
|
|
52
|
+
setError('');
|
|
53
|
+
|
|
54
|
+
try {
|
|
55
|
+
const keyfile = exportKeyfile(onboardingMnemonic);
|
|
56
|
+
const timestamp = new Date().toISOString().split('T')[0];
|
|
57
|
+
downloadKeyfile(keyfile, `nockchain-keyfile-${timestamp}.json`);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
setError('Failed to export keyfile');
|
|
60
|
+
console.error(err);
|
|
61
|
+
} finally {
|
|
62
|
+
setIsExporting(false);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
className="w-[357px] h-[600px] flex flex-col"
|
|
69
|
+
style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
|
|
70
|
+
>
|
|
71
|
+
{/* Header */}
|
|
72
|
+
<header
|
|
73
|
+
className="flex items-center justify-between px-4 py-3 min-h-[64px]"
|
|
74
|
+
style={{ backgroundColor: 'var(--color-bg)' }}
|
|
75
|
+
>
|
|
76
|
+
<button
|
|
77
|
+
className="w-8 h-8 flex items-center justify-center p-2 transition-opacity hover:opacity-70"
|
|
78
|
+
onClick={handleBack}
|
|
79
|
+
>
|
|
80
|
+
<ChevronLeftIcon className="w-5 h-5" />
|
|
81
|
+
</button>
|
|
82
|
+
<h1 className="font-sans font-medium text-base tracking-[0.16px] leading-[22px]">
|
|
83
|
+
View secret phrase
|
|
84
|
+
</h1>
|
|
85
|
+
<div className="w-8 h-8" />
|
|
86
|
+
</header>
|
|
87
|
+
|
|
88
|
+
{/* Content - Scrollable */}
|
|
89
|
+
<div className="flex-1 overflow-y-auto pt-2">
|
|
90
|
+
<div className="px-4 pb-4 flex flex-col gap-6">
|
|
91
|
+
{/* Title Section */}
|
|
92
|
+
<div className="flex flex-col items-center gap-3 w-full">
|
|
93
|
+
<div className="w-10 h-10 flex items-center justify-center">
|
|
94
|
+
<img src={LockIcon} alt="Lock" className="w-full h-full" />
|
|
95
|
+
</div>
|
|
96
|
+
<div className="flex flex-col gap-2 text-center w-full">
|
|
97
|
+
<h2 className="font-display font-medium text-2xl tracking-[-0.48px] leading-7">
|
|
98
|
+
View secret phrase
|
|
99
|
+
</h2>
|
|
100
|
+
<p
|
|
101
|
+
className="font-sans font-normal text-[13px] tracking-[0.26px] leading-[18px]"
|
|
102
|
+
style={{ color: 'var(--color-text-muted)' }}
|
|
103
|
+
>
|
|
104
|
+
Make sure no one is looking at your screen
|
|
105
|
+
</p>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
109
|
+
{/* Download Keyfile Link */}
|
|
110
|
+
<button
|
|
111
|
+
onClick={handleDownloadKeyfile}
|
|
112
|
+
disabled={isExporting}
|
|
113
|
+
className="font-sans font-medium text-sm tracking-[0.14px] leading-[18px] text-center underline hover:opacity-70 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed"
|
|
114
|
+
>
|
|
115
|
+
{isExporting ? 'Downloading...' : 'Download keyfile'}
|
|
116
|
+
</button>
|
|
117
|
+
|
|
118
|
+
{/* Secret Phrase Grid */}
|
|
119
|
+
<div className="relative flex flex-col gap-2">
|
|
120
|
+
{/* 12 rows of 2 words each */}
|
|
121
|
+
{[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22].map(startIndex => (
|
|
122
|
+
<div key={startIndex} className="flex gap-2">
|
|
123
|
+
{/* Left word */}
|
|
124
|
+
<div
|
|
125
|
+
className="flex-1 rounded-lg overflow-hidden"
|
|
126
|
+
style={{
|
|
127
|
+
backgroundColor: 'var(--color-bg)',
|
|
128
|
+
border: '1px solid var(--color-surface-800)',
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
<div className="flex items-center gap-2.5 p-2">
|
|
132
|
+
<div
|
|
133
|
+
className="w-7 h-7 rounded flex items-center justify-center flex-shrink-0"
|
|
134
|
+
style={{ backgroundColor: 'var(--color-surface-800)' }}
|
|
135
|
+
>
|
|
136
|
+
<span className="font-sans font-medium text-sm tracking-[0.14px] leading-[18px]">
|
|
137
|
+
{startIndex + 1}
|
|
138
|
+
</span>
|
|
139
|
+
</div>
|
|
140
|
+
<span className="font-sans font-medium text-sm tracking-[0.14px] leading-[18px] flex-1">
|
|
141
|
+
{secretPhrase[startIndex]}
|
|
142
|
+
</span>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
{/* Right word */}
|
|
147
|
+
<div
|
|
148
|
+
className="flex-1 rounded-lg overflow-hidden"
|
|
149
|
+
style={{
|
|
150
|
+
backgroundColor: 'var(--color-bg)',
|
|
151
|
+
border: '1px solid var(--color-surface-800)',
|
|
152
|
+
}}
|
|
153
|
+
>
|
|
154
|
+
<div className="flex items-center gap-2.5 p-2">
|
|
155
|
+
<div
|
|
156
|
+
className="w-7 h-7 rounded flex items-center justify-center flex-shrink-0"
|
|
157
|
+
style={{ backgroundColor: 'var(--color-surface-800)' }}
|
|
158
|
+
>
|
|
159
|
+
<span className="font-sans font-medium text-sm tracking-[0.14px] leading-[18px]">
|
|
160
|
+
{startIndex + 2}
|
|
161
|
+
</span>
|
|
162
|
+
</div>
|
|
163
|
+
<span className="font-sans font-medium text-sm tracking-[0.14px] leading-[18px] flex-1">
|
|
164
|
+
{secretPhrase[startIndex + 1]}
|
|
165
|
+
</span>
|
|
166
|
+
</div>
|
|
167
|
+
</div>
|
|
168
|
+
</div>
|
|
169
|
+
))}
|
|
170
|
+
|
|
171
|
+
{/* Blur Overlay */}
|
|
172
|
+
{!isRevealed && (
|
|
173
|
+
<div
|
|
174
|
+
className="absolute inset-0 backdrop-blur-[6px] rounded-lg"
|
|
175
|
+
style={{
|
|
176
|
+
backgroundColor: 'var(--color-popover)',
|
|
177
|
+
border: '1px solid var(--color-surface-900)',
|
|
178
|
+
}}
|
|
179
|
+
/>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
</div>
|
|
184
|
+
|
|
185
|
+
{/* Bottom Button - Pinned to bottom */}
|
|
186
|
+
<div
|
|
187
|
+
className="px-4 py-3 shrink-0"
|
|
188
|
+
style={{ borderTop: '1px solid var(--color-divider)', backgroundColor: 'var(--color-bg)' }}
|
|
189
|
+
>
|
|
190
|
+
<button
|
|
191
|
+
onClick={isRevealed ? handleCopySecretPhrase : handleReveal}
|
|
192
|
+
disabled={copiedSeed}
|
|
193
|
+
className="w-full h-12 rounded-lg font-sans font-medium text-sm tracking-[0.14px] leading-[18px] transition-opacity hover:opacity-90 flex items-center justify-center gap-2"
|
|
194
|
+
style={{
|
|
195
|
+
backgroundColor: 'var(--color-primary)',
|
|
196
|
+
color: '#000',
|
|
197
|
+
}}
|
|
198
|
+
>
|
|
199
|
+
{copiedSeed && <CheckIcon className="w-5 h-5" />}
|
|
200
|
+
{!isRevealed ? 'Show secret phrase' : copiedSeed ? 'Copied!' : 'Copy Secret Phrase'}
|
|
201
|
+
</button>
|
|
202
|
+
</div>
|
|
203
|
+
|
|
204
|
+
{/* Error message */}
|
|
205
|
+
{error && (
|
|
206
|
+
<div className="px-4 pb-3">
|
|
207
|
+
<Alert type="error">{error}</Alert>
|
|
208
|
+
</div>
|
|
209
|
+
)}
|
|
210
|
+
</div>
|
|
211
|
+
);
|
|
212
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { useStore } from '../store';
|
|
2
|
+
import { useState, useEffect } from 'react';
|
|
3
|
+
import { STORAGE_KEYS, INTERNAL_METHODS } from '../../shared/constants';
|
|
4
|
+
import { send } from '../utils/messaging';
|
|
5
|
+
import RoseLogo from '../assets/iris-logo.svg';
|
|
6
|
+
import { CloseIcon } from '../components/icons/CloseIcon';
|
|
7
|
+
|
|
8
|
+
export function WalletPermissionsScreen() {
|
|
9
|
+
const { navigate } = useStore();
|
|
10
|
+
const [approvedOrigins, setApprovedOrigins] = useState<string[]>([]);
|
|
11
|
+
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
loadApprovedOrigins();
|
|
14
|
+
}, []);
|
|
15
|
+
|
|
16
|
+
async function loadApprovedOrigins() {
|
|
17
|
+
const stored = (await chrome.storage.local.get([STORAGE_KEYS.APPROVED_ORIGINS])) as Record<
|
|
18
|
+
string,
|
|
19
|
+
unknown
|
|
20
|
+
>;
|
|
21
|
+
const raw = stored[STORAGE_KEYS.APPROVED_ORIGINS];
|
|
22
|
+
const origins = Array.isArray(raw) ? raw.filter((x): x is string => typeof x === 'string') : [];
|
|
23
|
+
setApprovedOrigins(origins);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function handleClose() {
|
|
27
|
+
navigate('home');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function handleRevoke(origin: string) {
|
|
31
|
+
try {
|
|
32
|
+
await send(INTERNAL_METHODS.REVOKE_ORIGIN, [{ origin }]);
|
|
33
|
+
await loadApprovedOrigins();
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('Failed to revoke origin:', error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<div
|
|
41
|
+
className="w-[357px] h-[600px] flex flex-col overflow-y-auto"
|
|
42
|
+
style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
|
|
43
|
+
>
|
|
44
|
+
{/* Header */}
|
|
45
|
+
<header
|
|
46
|
+
className="flex items-center justify-between px-4 py-3 min-h-[64px]"
|
|
47
|
+
style={{ backgroundColor: 'var(--color-bg)' }}
|
|
48
|
+
>
|
|
49
|
+
<div className="w-8 h-8 flex items-center justify-center shrink-0">
|
|
50
|
+
<img src={RoseLogo} alt="Rose" className="w-6 h-6" />
|
|
51
|
+
</div>
|
|
52
|
+
<h1 className="m-0 text-base font-medium leading-[22px] tracking-[0.16px]">
|
|
53
|
+
Wallet permissions
|
|
54
|
+
</h1>
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
onClick={handleClose}
|
|
58
|
+
className="w-8 h-8 rounded-lg flex items-center justify-center transition-colors focus:outline-none focus-visible:ring-2 shrink-0"
|
|
59
|
+
style={{ color: 'var(--color-text-primary)' }}
|
|
60
|
+
onMouseEnter={e => (e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')}
|
|
61
|
+
onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
|
|
62
|
+
aria-label="Close"
|
|
63
|
+
>
|
|
64
|
+
<CloseIcon />
|
|
65
|
+
</button>
|
|
66
|
+
</header>
|
|
67
|
+
|
|
68
|
+
{/* Content */}
|
|
69
|
+
<div className="flex flex-col gap-2 h-[536px] overflow-y-auto">
|
|
70
|
+
<div className="flex flex-col gap-2 px-4 py-2">
|
|
71
|
+
{approvedOrigins && approvedOrigins.length > 0 ? (
|
|
72
|
+
approvedOrigins.map(origin => (
|
|
73
|
+
<div
|
|
74
|
+
key={origin}
|
|
75
|
+
className="flex items-center justify-between p-2 rounded-lg transition-colors"
|
|
76
|
+
onMouseEnter={e =>
|
|
77
|
+
(e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')
|
|
78
|
+
}
|
|
79
|
+
onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
|
|
80
|
+
>
|
|
81
|
+
<div className="flex items-center gap-2.5 flex-1 min-w-0">
|
|
82
|
+
<div className="w-8 h-8 rounded-lg flex items-center justify-center overflow-hidden shrink-0">
|
|
83
|
+
{/* Site icon placeholder (first letter) */}
|
|
84
|
+
<div
|
|
85
|
+
className="w-5 h-5 flex items-center justify-center text-[12px] font-semibold"
|
|
86
|
+
style={{ color: 'var(--color-text-muted)' }}
|
|
87
|
+
>
|
|
88
|
+
{origin.charAt(0).toUpperCase()}
|
|
89
|
+
</div>
|
|
90
|
+
</div>
|
|
91
|
+
<span className="text-sm font-medium leading-[18px] tracking-[0.14px] truncate">
|
|
92
|
+
{origin}
|
|
93
|
+
</span>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
title="Revoke permissions"
|
|
99
|
+
onClick={() => handleRevoke(origin)}
|
|
100
|
+
className="w-8 h-8 rounded-lg flex items-center justify-center p-1.5 transition-colors focus:outline-none focus-visible:ring-2 shrink-0"
|
|
101
|
+
style={{ backgroundColor: 'var(--color-red-light)' }}
|
|
102
|
+
onMouseEnter={e => (e.currentTarget.style.opacity = '0.8')}
|
|
103
|
+
onMouseLeave={e => (e.currentTarget.style.opacity = '1')}
|
|
104
|
+
>
|
|
105
|
+
<CloseIcon className="w-4 h-4 [filter:brightness(0)_saturate(100%)_invert(29%)_sepia(96%)_saturate(2447%)_hue-rotate(347deg)_brightness(92%)_contrast(93%)]" />
|
|
106
|
+
</button>
|
|
107
|
+
</div>
|
|
108
|
+
))
|
|
109
|
+
) : (
|
|
110
|
+
<div className="flex items-center justify-center text-center px-4 py-10">
|
|
111
|
+
<p
|
|
112
|
+
className="m-0 text-sm font-normal leading-[18px] tracking-[0.14px]"
|
|
113
|
+
style={{ color: 'var(--color-text-muted)' }}
|
|
114
|
+
>
|
|
115
|
+
No connected sites
|
|
116
|
+
</p>
|
|
117
|
+
</div>
|
|
118
|
+
)}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|