@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,164 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useStore } from '../store';
|
|
3
|
+
import { send } from '../utils/messaging';
|
|
4
|
+
import { INTERNAL_METHODS } from '../../shared/constants';
|
|
5
|
+
import RoseLogo96 from '../assets/iris-logo-96.svg';
|
|
6
|
+
import { ChevronLeftIcon } from '../components/icons/ChevronLeftIcon';
|
|
7
|
+
import { EyeIcon } from '../components/icons/EyeIcon';
|
|
8
|
+
import { EyeOffIcon } from '../components/icons/EyeOffIcon';
|
|
9
|
+
import { formatWalletError } from '../utils/formatWalletError';
|
|
10
|
+
|
|
11
|
+
export function KeySettingsPasswordScreen() {
|
|
12
|
+
const { navigate, setOnboardingMnemonic } = useStore();
|
|
13
|
+
const [password, setPassword] = useState('');
|
|
14
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
15
|
+
const [error, setError] = useState('');
|
|
16
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
17
|
+
|
|
18
|
+
function handleBack() {
|
|
19
|
+
navigate('settings');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function handleConfirm() {
|
|
23
|
+
if (!password) {
|
|
24
|
+
setError('Please enter your password');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
setIsLoading(true);
|
|
29
|
+
setError('');
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const result = await send<{ ok?: boolean; mnemonic?: string; error?: string }>(
|
|
33
|
+
INTERNAL_METHODS.GET_MNEMONIC,
|
|
34
|
+
[password]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (result?.error) {
|
|
38
|
+
setError(formatWalletError(result.error));
|
|
39
|
+
setPassword('');
|
|
40
|
+
} else if (result?.mnemonic) {
|
|
41
|
+
// Store mnemonic temporarily for viewing
|
|
42
|
+
setOnboardingMnemonic(result.mnemonic);
|
|
43
|
+
navigate('view-secret-phrase');
|
|
44
|
+
}
|
|
45
|
+
} catch (err) {
|
|
46
|
+
setError('Failed to retrieve secret phrase');
|
|
47
|
+
console.error('Failed to get mnemonic:', err);
|
|
48
|
+
} finally {
|
|
49
|
+
setIsLoading(false);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className="w-[357px] h-[600px] flex flex-col overflow-y-auto"
|
|
56
|
+
style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
|
|
57
|
+
>
|
|
58
|
+
{/* Header */}
|
|
59
|
+
<header
|
|
60
|
+
className="flex items-center justify-between px-4 py-3 min-h-[64px]"
|
|
61
|
+
style={{ backgroundColor: 'var(--color-bg)' }}
|
|
62
|
+
>
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
onClick={handleBack}
|
|
66
|
+
aria-label="Back"
|
|
67
|
+
className="w-8 h-8 p-2 flex items-center justify-center rounded-lg transition-colors focus:outline-none focus-visible:ring-2"
|
|
68
|
+
style={{ backgroundColor: 'transparent', color: 'var(--color-text-primary)' }}
|
|
69
|
+
onMouseEnter={e => (e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')}
|
|
70
|
+
onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
|
|
71
|
+
>
|
|
72
|
+
<ChevronLeftIcon className="w-5 h-5" />
|
|
73
|
+
</button>
|
|
74
|
+
<h1 className="m-0 text-base font-medium leading-[22px] tracking-[0.16px]">Key settings</h1>
|
|
75
|
+
<div className="w-8 h-8" />
|
|
76
|
+
</header>
|
|
77
|
+
|
|
78
|
+
<div className="flex flex-1 flex-col justify-between px-4 py-8">
|
|
79
|
+
{/* Content */}
|
|
80
|
+
<div className="flex flex-col items-center gap-3 w-full">
|
|
81
|
+
<img src={RoseLogo96} alt="Rose" className="w-24 h-24" />
|
|
82
|
+
|
|
83
|
+
<p
|
|
84
|
+
className="m-0 text-[13px] leading-[18px] tracking-[0.26px] text-center"
|
|
85
|
+
style={{ color: 'var(--color-text-muted)' }}
|
|
86
|
+
>
|
|
87
|
+
Please re-enter your password to see your keys
|
|
88
|
+
</p>
|
|
89
|
+
</div>
|
|
90
|
+
|
|
91
|
+
{/* Form */}
|
|
92
|
+
<div className="flex flex-col gap-4 w-full mt-8">
|
|
93
|
+
<div className="flex flex-col gap-[6px] w-full">
|
|
94
|
+
<label className="text-[13px] leading-[18px] tracking-[0.26px] font-medium">
|
|
95
|
+
Password
|
|
96
|
+
</label>
|
|
97
|
+
|
|
98
|
+
<div className="relative w-full">
|
|
99
|
+
<input
|
|
100
|
+
type={showPassword ? 'text' : 'password'}
|
|
101
|
+
className="w-full h-[52px] bg-transparent rounded-lg pl-3 pr-10 py-4 outline-none transition-colors text-sm leading-[18px] tracking-[0.14px] font-medium"
|
|
102
|
+
style={{
|
|
103
|
+
border: '1px solid var(--color-surface-700)',
|
|
104
|
+
color: 'var(--color-text-primary)',
|
|
105
|
+
}}
|
|
106
|
+
placeholder="Enter your password"
|
|
107
|
+
value={password}
|
|
108
|
+
onChange={e => {
|
|
109
|
+
setPassword(e.target.value);
|
|
110
|
+
setError('');
|
|
111
|
+
}}
|
|
112
|
+
onKeyDown={e => {
|
|
113
|
+
if (e.key === 'Enter') handleConfirm();
|
|
114
|
+
}}
|
|
115
|
+
onFocus={e => (e.currentTarget.style.borderColor = 'var(--color-primary)')}
|
|
116
|
+
onBlur={e => (e.currentTarget.style.borderColor = 'var(--color-surface-700)')}
|
|
117
|
+
disabled={isLoading}
|
|
118
|
+
/>
|
|
119
|
+
<button
|
|
120
|
+
type="button"
|
|
121
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
122
|
+
className="absolute right-3 top-1/2 -translate-y-1/2 p-1 flex items-center justify-center transition-colors"
|
|
123
|
+
style={{ color: 'var(--color-text-muted)' }}
|
|
124
|
+
onMouseEnter={e => (e.currentTarget.style.color = 'var(--color-text-primary)')}
|
|
125
|
+
onMouseLeave={e => (e.currentTarget.style.color = 'var(--color-text-muted)')}
|
|
126
|
+
aria-label={showPassword ? 'Hide password' : 'Show password'}
|
|
127
|
+
>
|
|
128
|
+
{showPassword ? (
|
|
129
|
+
<EyeIcon className="w-4 h-4" />
|
|
130
|
+
) : (
|
|
131
|
+
<EyeOffIcon className="w-4 h-4" />
|
|
132
|
+
)}
|
|
133
|
+
</button>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{error && (
|
|
137
|
+
<p className="text-xs" style={{ color: 'var(--color-red)' }}>
|
|
138
|
+
{error}
|
|
139
|
+
</p>
|
|
140
|
+
)}
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<button
|
|
144
|
+
onClick={handleConfirm}
|
|
145
|
+
disabled={isLoading}
|
|
146
|
+
className="w-full h-12 rounded-lg text-sm font-medium leading-[18px] tracking-[0.14px] transition-opacity hover:opacity-90 active:opacity-80 disabled:opacity-50"
|
|
147
|
+
style={{ backgroundColor: 'var(--color-primary)', color: '#000' }}
|
|
148
|
+
>
|
|
149
|
+
{isLoading ? 'Verifying...' : 'Confirm'}
|
|
150
|
+
</button>
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
{/* Warning */}
|
|
154
|
+
<p
|
|
155
|
+
className="m-0 text-[13px] leading-[18px] tracking-[0.26px] text-center"
|
|
156
|
+
style={{ color: 'var(--color-text-muted)' }}
|
|
157
|
+
>
|
|
158
|
+
Warning: Never disclose this key. Anyone with your private keys can steal any assets held
|
|
159
|
+
in your account.
|
|
160
|
+
</p>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { useStore } from '../store';
|
|
3
|
+
import { send } from '../utils/messaging';
|
|
4
|
+
import { INTERNAL_METHODS } from '../../shared/constants';
|
|
5
|
+
import { ChevronLeftIcon } from '../components/icons/ChevronLeftIcon';
|
|
6
|
+
|
|
7
|
+
type LockTimeOption = '1min' | '5min' | '10min' | '15min' | '30min' | '1hour' | '4hours' | 'never';
|
|
8
|
+
|
|
9
|
+
// Conversion helpers
|
|
10
|
+
function minutesToOption(minutes: number): LockTimeOption {
|
|
11
|
+
if (minutes === 1) return '1min';
|
|
12
|
+
if (minutes === 5) return '5min';
|
|
13
|
+
if (minutes === 10) return '10min';
|
|
14
|
+
if (minutes === 15) return '15min';
|
|
15
|
+
if (minutes === 30) return '30min';
|
|
16
|
+
if (minutes === 60) return '1hour';
|
|
17
|
+
if (minutes === 240) return '4hours';
|
|
18
|
+
if (minutes === 0) return 'never';
|
|
19
|
+
return '15min'; // default
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function optionToMinutes(option: LockTimeOption): number {
|
|
23
|
+
switch (option) {
|
|
24
|
+
case '1min':
|
|
25
|
+
return 1;
|
|
26
|
+
case '5min':
|
|
27
|
+
return 5;
|
|
28
|
+
case '10min':
|
|
29
|
+
return 10;
|
|
30
|
+
case '15min':
|
|
31
|
+
return 15;
|
|
32
|
+
case '30min':
|
|
33
|
+
return 30;
|
|
34
|
+
case '1hour':
|
|
35
|
+
return 60;
|
|
36
|
+
case '4hours':
|
|
37
|
+
return 240;
|
|
38
|
+
case 'never':
|
|
39
|
+
return 0;
|
|
40
|
+
default:
|
|
41
|
+
return 15;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* LockTimeScreen - Lock time settings screen
|
|
47
|
+
* Allows users to set the auto-lock timeout duration
|
|
48
|
+
*/
|
|
49
|
+
export function LockTimeScreen() {
|
|
50
|
+
const { navigate } = useStore();
|
|
51
|
+
const [selectedTime, setSelectedTime] = useState<LockTimeOption>('15min');
|
|
52
|
+
|
|
53
|
+
// Load current lock time setting on mount
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
(async () => {
|
|
56
|
+
const result = await send<{ minutes?: number }>(INTERNAL_METHODS.GET_AUTO_LOCK);
|
|
57
|
+
if (result?.minutes !== undefined) {
|
|
58
|
+
setSelectedTime(minutesToOption(result.minutes));
|
|
59
|
+
}
|
|
60
|
+
})();
|
|
61
|
+
}, []);
|
|
62
|
+
|
|
63
|
+
function handleBack() {
|
|
64
|
+
navigate('settings');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function handleTimeSelect(time: LockTimeOption) {
|
|
68
|
+
setSelectedTime(time);
|
|
69
|
+
|
|
70
|
+
// Convert to minutes and persist to backend
|
|
71
|
+
const minutes = optionToMinutes(time);
|
|
72
|
+
await send(INTERNAL_METHODS.SET_AUTO_LOCK, [minutes]);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const timeOptions: { value: LockTimeOption; label: string }[] = [
|
|
76
|
+
{ value: '1min', label: '1 minute' },
|
|
77
|
+
{ value: '5min', label: '5 minutes' },
|
|
78
|
+
{ value: '10min', label: '10 minutes' },
|
|
79
|
+
{ value: '15min', label: '15 minutes' },
|
|
80
|
+
{ value: '30min', label: '30 minutes' },
|
|
81
|
+
{ value: '1hour', label: '1 hour' },
|
|
82
|
+
{ value: '4hours', label: '4 hours' },
|
|
83
|
+
{ value: 'never', label: 'Never' },
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div
|
|
88
|
+
className="w-[357px] h-[600px] flex flex-col overflow-y-auto"
|
|
89
|
+
style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
|
|
90
|
+
>
|
|
91
|
+
{/* Header */}
|
|
92
|
+
<header
|
|
93
|
+
className="flex items-center justify-between px-4 py-3 min-h-[64px]"
|
|
94
|
+
style={{ backgroundColor: 'var(--color-bg)' }}
|
|
95
|
+
>
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
onClick={handleBack}
|
|
99
|
+
aria-label="Back"
|
|
100
|
+
className="w-8 h-8 p-2 flex items-center justify-center rounded-lg transition-colors focus:outline-none focus-visible:ring-2"
|
|
101
|
+
style={{ color: 'var(--color-text-primary)' }}
|
|
102
|
+
onMouseEnter={e => (e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')}
|
|
103
|
+
onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
|
|
104
|
+
>
|
|
105
|
+
<ChevronLeftIcon className="w-5 h-5" />
|
|
106
|
+
</button>
|
|
107
|
+
|
|
108
|
+
<h1 className="m-0 text-base font-medium leading-[22px] tracking-[0.16px]">Lock time</h1>
|
|
109
|
+
|
|
110
|
+
<div className="w-8 h-8" />
|
|
111
|
+
</header>
|
|
112
|
+
|
|
113
|
+
{/* Time Options */}
|
|
114
|
+
<div className="flex flex-col gap-2 px-3 py-2">
|
|
115
|
+
{timeOptions.map(option => {
|
|
116
|
+
const selected = selectedTime === option.value;
|
|
117
|
+
return (
|
|
118
|
+
<button
|
|
119
|
+
key={option.value}
|
|
120
|
+
type="button"
|
|
121
|
+
onClick={() => handleTimeSelect(option.value)}
|
|
122
|
+
className="flex items-center justify-between p-3 rounded-lg text-left transition-colors focus:outline-none focus-visible:ring-2"
|
|
123
|
+
onMouseEnter={e =>
|
|
124
|
+
(e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')
|
|
125
|
+
}
|
|
126
|
+
onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
|
|
127
|
+
role="radio"
|
|
128
|
+
aria-checked={selected}
|
|
129
|
+
>
|
|
130
|
+
<span className="text-sm font-medium leading-[18px] tracking-[0.14px]">
|
|
131
|
+
{option.label}
|
|
132
|
+
</span>
|
|
133
|
+
|
|
134
|
+
{/* Radio */}
|
|
135
|
+
<span
|
|
136
|
+
className="w-5 h-5 rounded-full flex items-center justify-center transition-colors"
|
|
137
|
+
style={{
|
|
138
|
+
border: `2px solid ${selected ? 'var(--color-primary)' : 'var(--color-surface-700)'}`,
|
|
139
|
+
}}
|
|
140
|
+
aria-hidden="true"
|
|
141
|
+
>
|
|
142
|
+
{selected && (
|
|
143
|
+
<span
|
|
144
|
+
className="w-2.5 h-2.5 rounded-full"
|
|
145
|
+
style={{ backgroundColor: 'var(--color-primary)' }}
|
|
146
|
+
/>
|
|
147
|
+
)}
|
|
148
|
+
</span>
|
|
149
|
+
</button>
|
|
150
|
+
);
|
|
151
|
+
})}
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useStore } from '../store';
|
|
3
|
+
import RoseLogo40 from '../assets/iris-logo-40.svg';
|
|
4
|
+
import { ChevronLeftIcon } from '../components/icons/ChevronLeftIcon';
|
|
5
|
+
|
|
6
|
+
export function ReceiveScreen() {
|
|
7
|
+
const { navigate, wallet } = useStore();
|
|
8
|
+
const [copySuccess, setCopySuccess] = useState(false);
|
|
9
|
+
|
|
10
|
+
// Get address from current account
|
|
11
|
+
const address = wallet.currentAccount?.address || wallet.address || '';
|
|
12
|
+
const addressStart = address.slice(0, 6);
|
|
13
|
+
const addressMiddle = address.slice(6, -5);
|
|
14
|
+
const addressEnd = address.slice(-5);
|
|
15
|
+
|
|
16
|
+
function handleBack() {
|
|
17
|
+
navigate('home');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function handleCopyAddress() {
|
|
21
|
+
try {
|
|
22
|
+
await navigator.clipboard.writeText(address);
|
|
23
|
+
setCopySuccess(true);
|
|
24
|
+
setTimeout(() => setCopySuccess(false), 2000);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error('Failed to copy address:', err);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return (
|
|
31
|
+
<div
|
|
32
|
+
className="w-[357px] h-[600px] flex flex-col overflow-y-auto"
|
|
33
|
+
style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
|
|
34
|
+
>
|
|
35
|
+
{/* Header */}
|
|
36
|
+
<header
|
|
37
|
+
className="flex items-center justify-between px-4 py-3 min-h-[64px]"
|
|
38
|
+
style={{ backgroundColor: 'var(--color-bg)' }}
|
|
39
|
+
>
|
|
40
|
+
<button
|
|
41
|
+
type="button"
|
|
42
|
+
onClick={handleBack}
|
|
43
|
+
aria-label="Back"
|
|
44
|
+
className="w-8 h-8 p-2 flex items-center justify-center rounded-lg transition-colors focus:outline-none focus-visible:ring-2"
|
|
45
|
+
style={{ backgroundColor: 'transparent' }}
|
|
46
|
+
onMouseEnter={e => (e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')}
|
|
47
|
+
onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
|
|
48
|
+
>
|
|
49
|
+
<ChevronLeftIcon className="w-5 h-5" />
|
|
50
|
+
</button>
|
|
51
|
+
<h1 className="m-0 text-base font-medium leading-[22px] tracking-[0.16px]">Receive NOCK</h1>
|
|
52
|
+
<div className="w-8 h-8" />
|
|
53
|
+
</header>
|
|
54
|
+
|
|
55
|
+
{/* Content */}
|
|
56
|
+
<div className="flex flex-col gap-2 h-[536px]" style={{ backgroundColor: 'var(--color-bg)' }}>
|
|
57
|
+
<div className="flex flex-col gap-8 px-4 py-2">
|
|
58
|
+
{/* Intro */}
|
|
59
|
+
<div className="flex flex-col items-center gap-3 w-full">
|
|
60
|
+
<img src={RoseLogo40} alt="Rose" className="w-10 h-10" />
|
|
61
|
+
|
|
62
|
+
<h2
|
|
63
|
+
className="m-0 text-2xl font-medium leading-7 tracking-[-0.48px] text-center font-display"
|
|
64
|
+
style={{ color: 'var(--color-text-primary)' }}
|
|
65
|
+
>
|
|
66
|
+
Your address
|
|
67
|
+
</h2>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
{/* Address + Copy */}
|
|
71
|
+
<div className="flex flex-col gap-4 w-full">
|
|
72
|
+
<div
|
|
73
|
+
className="rounded-lg px-3 pt-5 pb-3 flex flex-col items-center gap-5"
|
|
74
|
+
style={{ backgroundColor: 'var(--color-surface-800)' }}
|
|
75
|
+
>
|
|
76
|
+
<div className="text-sm leading-[18px] tracking-[0.14px] font-medium text-center break-words">
|
|
77
|
+
<span style={{ color: 'var(--color-text-primary)' }}>{addressStart}</span>
|
|
78
|
+
<span style={{ color: 'var(--color-text-muted)' }}>
|
|
79
|
+
{addressMiddle.substring(0, 18)}
|
|
80
|
+
</span>
|
|
81
|
+
<br />
|
|
82
|
+
<span style={{ color: 'var(--color-text-muted)' }}>
|
|
83
|
+
{addressMiddle.substring(18)}
|
|
84
|
+
</span>
|
|
85
|
+
<span style={{ color: 'var(--color-text-primary)' }}>{addressEnd}</span>
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
onClick={handleCopyAddress}
|
|
91
|
+
className="inline-flex items-center justify-center gap-[6px] py-[7px] pr-3 pl-4 bg-transparent rounded-full text-sm font-medium leading-[18px] tracking-[0.14px] transition active:opacity-80 focus:outline-none focus-visible:ring-2"
|
|
92
|
+
style={{
|
|
93
|
+
border: '1px solid var(--color-text-primary)',
|
|
94
|
+
color: 'var(--color-text-primary)',
|
|
95
|
+
}}
|
|
96
|
+
onMouseEnter={e =>
|
|
97
|
+
(e.currentTarget.style.backgroundColor = 'var(--color-surface-700)')
|
|
98
|
+
}
|
|
99
|
+
onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'transparent')}
|
|
100
|
+
>
|
|
101
|
+
<svg
|
|
102
|
+
width="16"
|
|
103
|
+
height="16"
|
|
104
|
+
viewBox="0 0 16 16"
|
|
105
|
+
fill="none"
|
|
106
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
107
|
+
className="shrink-0"
|
|
108
|
+
>
|
|
109
|
+
<path
|
|
110
|
+
d="M10.6667 1.33334H2.66667C1.93334 1.33334 1.34 1.93334 1.34 2.66667L1.33334 11.3333C1.33334 12.0667 1.92667 12.6667 2.66 12.6667H10.6667C11.4 12.6667 12 12.0667 12 11.3333V2.66667C12 1.93334 11.4 1.33334 10.6667 1.33334ZM10.6667 11.3333H2.66667V2.66667H10.6667V11.3333ZM13.3333 5.33334V14C13.3333 14.7333 12.7333 15.3333 12 15.3333H4.66667C4.66667 15.3333 4.66667 14.6667 4.66667 14.6667H12.6667V5.33334C12.6667 5.33334 13.3333 5.33334 13.3333 5.33334Z"
|
|
111
|
+
fill="currentColor"
|
|
112
|
+
/>
|
|
113
|
+
</svg>
|
|
114
|
+
{copySuccess ? 'Copied!' : 'Copy address'}
|
|
115
|
+
</button>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
{/* Instructions */}
|
|
119
|
+
<div
|
|
120
|
+
className="rounded-lg p-3 flex flex-col gap-2.5"
|
|
121
|
+
style={{ border: '1px solid var(--color-divider)' }}
|
|
122
|
+
>
|
|
123
|
+
<h3
|
|
124
|
+
className="m-0 text-sm font-medium leading-[18px] tracking-[0.14px] font-display"
|
|
125
|
+
style={{ color: 'var(--color-text-primary)' }}
|
|
126
|
+
>
|
|
127
|
+
How to receive NOCK:
|
|
128
|
+
</h3>
|
|
129
|
+
<ul className="m-0 p-0 flex flex-col">
|
|
130
|
+
<li
|
|
131
|
+
className="text-[13px] leading-[18px] tracking-[0.26px] font-medium relative pl-0 before:content-['•'] before:mr-1 before:inline-block"
|
|
132
|
+
style={{ color: 'var(--color-text-primary)' }}
|
|
133
|
+
>
|
|
134
|
+
Share this address with the sender
|
|
135
|
+
</li>
|
|
136
|
+
<li
|
|
137
|
+
className="text-[13px] leading-[18px] tracking-[0.26px] font-medium relative pl-0 before:content-['•'] before:mr-1 before:inline-block"
|
|
138
|
+
style={{ color: 'var(--color-text-primary)' }}
|
|
139
|
+
>
|
|
140
|
+
Transactions will appear in your wallet
|
|
141
|
+
</li>
|
|
142
|
+
</ul>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</div>
|
|
147
|
+
</div>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recovery Phrase Screen - View wallet's secret phrase
|
|
3
|
+
* Requires password confirmation for security
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState } from 'react';
|
|
7
|
+
import { useStore } from '../store';
|
|
8
|
+
import { ScreenContainer } from '../components/ScreenContainer';
|
|
9
|
+
import { Alert } from '../components/Alert';
|
|
10
|
+
import { PasswordInput } from '../components/PasswordInput';
|
|
11
|
+
import { send } from '../utils/messaging';
|
|
12
|
+
import { INTERNAL_METHODS } from '../../shared/constants';
|
|
13
|
+
import { ChevronLeftIcon } from '../components/icons/ChevronLeftIcon';
|
|
14
|
+
import { EyeIcon } from '../components/icons/EyeIcon';
|
|
15
|
+
import { formatWalletError } from '../utils/formatWalletError';
|
|
16
|
+
|
|
17
|
+
export function RecoveryPhraseScreen() {
|
|
18
|
+
const { navigate } = useStore();
|
|
19
|
+
const [password, setPassword] = useState('');
|
|
20
|
+
const [error, setError] = useState('');
|
|
21
|
+
const [mnemonic, setMnemonic] = useState<string | null>(null);
|
|
22
|
+
const [isRevealed, setIsRevealed] = useState(false);
|
|
23
|
+
|
|
24
|
+
async function handleReveal() {
|
|
25
|
+
setError('');
|
|
26
|
+
|
|
27
|
+
if (!password) {
|
|
28
|
+
setError('Please enter your password');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const result = await send<{ ok?: boolean; mnemonic?: string; error?: string }>(
|
|
33
|
+
INTERNAL_METHODS.GET_MNEMONIC,
|
|
34
|
+
[password]
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (result?.error) {
|
|
38
|
+
setError(formatWalletError(result.error));
|
|
39
|
+
setPassword('');
|
|
40
|
+
} else {
|
|
41
|
+
setMnemonic(result.mnemonic || '');
|
|
42
|
+
setIsRevealed(true);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const words = mnemonic ? mnemonic.split(' ') : [];
|
|
47
|
+
|
|
48
|
+
// Password confirmation view
|
|
49
|
+
if (!isRevealed) {
|
|
50
|
+
return (
|
|
51
|
+
<div
|
|
52
|
+
className="w-[357px] h-[600px] flex flex-col p-4"
|
|
53
|
+
style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
|
|
54
|
+
>
|
|
55
|
+
{/* Header */}
|
|
56
|
+
<div className="flex items-center gap-3 mb-6">
|
|
57
|
+
<button
|
|
58
|
+
onClick={() => navigate('settings')}
|
|
59
|
+
className="transition-colors"
|
|
60
|
+
style={{ color: 'var(--color-text-primary)' }}
|
|
61
|
+
>
|
|
62
|
+
<ChevronLeftIcon />
|
|
63
|
+
</button>
|
|
64
|
+
<h2 className="text-xl font-semibold">View Secret Phrase</h2>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div
|
|
68
|
+
className="mb-6 p-3 rounded-lg"
|
|
69
|
+
style={{ backgroundColor: 'var(--color-red-light)', color: 'var(--color-red)' }}
|
|
70
|
+
>
|
|
71
|
+
<strong>Warning:</strong> Never share your secret phrase with anyone. Anyone with access
|
|
72
|
+
to this phrase can access your funds.
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<p className="text-sm mb-4" style={{ color: 'var(--color-text-muted)' }}>
|
|
76
|
+
Enter your password to reveal your 24-word secret phrase.
|
|
77
|
+
</p>
|
|
78
|
+
|
|
79
|
+
<input
|
|
80
|
+
type="password"
|
|
81
|
+
value={password}
|
|
82
|
+
onChange={e => {
|
|
83
|
+
setPassword(e.target.value);
|
|
84
|
+
setError('');
|
|
85
|
+
}}
|
|
86
|
+
placeholder="Password"
|
|
87
|
+
className="my-2 w-full rounded-lg px-4 py-3 outline-none"
|
|
88
|
+
style={{
|
|
89
|
+
border: '1px solid var(--color-surface-700)',
|
|
90
|
+
backgroundColor: 'var(--color-bg)',
|
|
91
|
+
color: 'var(--color-text-primary)',
|
|
92
|
+
}}
|
|
93
|
+
onKeyDown={e => e.key === 'Enter' && handleReveal()}
|
|
94
|
+
onFocus={e => (e.currentTarget.style.borderColor = 'var(--color-primary)')}
|
|
95
|
+
onBlur={e => (e.currentTarget.style.borderColor = 'var(--color-surface-700)')}
|
|
96
|
+
autoFocus
|
|
97
|
+
/>
|
|
98
|
+
|
|
99
|
+
{error && (
|
|
100
|
+
<div
|
|
101
|
+
className="my-2 p-3 rounded-lg"
|
|
102
|
+
style={{ backgroundColor: 'var(--color-red-light)', color: 'var(--color-red)' }}
|
|
103
|
+
>
|
|
104
|
+
{error}
|
|
105
|
+
</div>
|
|
106
|
+
)}
|
|
107
|
+
|
|
108
|
+
<button
|
|
109
|
+
onClick={handleReveal}
|
|
110
|
+
className="my-2 w-full rounded-lg px-5 py-3.5 text-sm font-medium transition-opacity hover:opacity-90"
|
|
111
|
+
style={{ backgroundColor: 'var(--color-primary)', color: '#000' }}
|
|
112
|
+
>
|
|
113
|
+
<EyeIcon className="w-4 h-4 inline mr-2" />
|
|
114
|
+
Reveal Secret Phrase
|
|
115
|
+
</button>
|
|
116
|
+
|
|
117
|
+
<button
|
|
118
|
+
onClick={() => navigate('settings')}
|
|
119
|
+
className="my-2 w-full rounded-lg px-5 py-3.5 text-sm font-medium transition-colors"
|
|
120
|
+
style={{ backgroundColor: 'var(--color-surface-800)' }}
|
|
121
|
+
onMouseEnter={e => (e.currentTarget.style.backgroundColor = 'var(--color-surface-700)')}
|
|
122
|
+
onMouseLeave={e => (e.currentTarget.style.backgroundColor = 'var(--color-surface-800)')}
|
|
123
|
+
>
|
|
124
|
+
Cancel
|
|
125
|
+
</button>
|
|
126
|
+
</div>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Secret phrase display view
|
|
131
|
+
return (
|
|
132
|
+
<div
|
|
133
|
+
className="w-[357px] h-[600px] flex flex-col p-4"
|
|
134
|
+
style={{ backgroundColor: 'var(--color-bg)', color: 'var(--color-text-primary)' }}
|
|
135
|
+
>
|
|
136
|
+
{/* Header */}
|
|
137
|
+
<div className="flex items-center gap-3 mb-4">
|
|
138
|
+
<button
|
|
139
|
+
onClick={() => navigate('settings')}
|
|
140
|
+
className="transition-colors"
|
|
141
|
+
style={{ color: 'var(--color-text-primary)' }}
|
|
142
|
+
>
|
|
143
|
+
<ChevronLeftIcon />
|
|
144
|
+
</button>
|
|
145
|
+
<h2 className="text-xl font-semibold">Secret Phrase</h2>
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
<div
|
|
149
|
+
className="mb-4 p-3 rounded-lg"
|
|
150
|
+
style={{ backgroundColor: 'var(--color-red-light)', color: 'var(--color-red)' }}
|
|
151
|
+
>
|
|
152
|
+
<strong>Warning:</strong> Write down these 24 words in order and store them safely. Never
|
|
153
|
+
share them with anyone.
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
{/* Words grid */}
|
|
157
|
+
<div className="flex-1 overflow-y-auto mb-4">
|
|
158
|
+
<div className="grid grid-cols-2 gap-2">
|
|
159
|
+
{words.map((word, index) => (
|
|
160
|
+
<div
|
|
161
|
+
key={index}
|
|
162
|
+
className="rounded p-2 flex items-center gap-2"
|
|
163
|
+
style={{ backgroundColor: 'var(--color-surface-800)' }}
|
|
164
|
+
>
|
|
165
|
+
<span className="text-xs w-6" style={{ color: 'var(--color-text-muted)' }}>
|
|
166
|
+
{index + 1}.
|
|
167
|
+
</span>
|
|
168
|
+
<span className="text-sm font-mono">{word}</span>
|
|
169
|
+
</div>
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<button
|
|
175
|
+
onClick={() => navigate('settings')}
|
|
176
|
+
className="w-full rounded-lg px-5 py-3.5 text-sm font-medium transition-opacity hover:opacity-90"
|
|
177
|
+
style={{ backgroundColor: 'var(--color-primary)', color: '#000' }}
|
|
178
|
+
>
|
|
179
|
+
Done
|
|
180
|
+
</button>
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|