auramaxx 1.0.0-alpha.4
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/LICENSE +26 -0
- package/README.md +112 -0
- package/bin/aurawallet.js +121 -0
- package/docs/ADAPTERS.md +467 -0
- package/docs/API.md +2679 -0
- package/docs/APPS.md +198 -0
- package/docs/ARCHITECTURE.md +350 -0
- package/docs/AUTH.md +698 -0
- package/docs/BEST-PRACTICES.md +121 -0
- package/docs/CLI.md +61 -0
- package/docs/DEVELOPING-APPS.md +452 -0
- package/docs/EXTENSION.md +97 -0
- package/docs/JOBS.md +33 -0
- package/docs/MCP.md +76 -0
- package/docs/PROTOCOL.md +142 -0
- package/docs/SETUP.md +219 -0
- package/docs/WORKSPACE.md +672 -0
- package/docs/agent-auth.md +63 -0
- package/docs/aura-file.md +48 -0
- package/docs/credentials.md +53 -0
- package/docs/external/getting-started.md +65 -0
- package/docs/external/overview.md +45 -0
- package/docs/external/use-cases.md +48 -0
- package/docs/external/why-aura.md +35 -0
- package/docs/jobs/connect-agent.md +77 -0
- package/docs/jobs/migrate-from-dotenv.md +79 -0
- package/docs/jobs/recover-from-lockout.md +72 -0
- package/docs/jobs/secure-ci.md +63 -0
- package/docs/oauth2.md +42 -0
- package/docs/passkeys.md +60 -0
- package/docs/security.md +540 -0
- package/docs/specs/aura-open-protocol.md +61 -0
- package/docs/specs/aura-provider-plugin.md +24 -0
- package/docs/specs/aura-registry-model.md +31 -0
- package/docs/specs/fixtures/invalid-bad-key.aura +1 -0
- package/docs/specs/fixtures/invalid-bad-unicode-escape.aura +1 -0
- package/docs/specs/fixtures/invalid-duplicate-key.aura +2 -0
- package/docs/specs/fixtures/valid-basic.aura +4 -0
- package/docs/specs/fixtures/valid-provider-ref.aura +1 -0
- package/docs/specs/fixtures/valid-quoted-escapes.aura +2 -0
- package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -0
- package/docs/totp.md +40 -0
- package/docs/wallet/AI.md +508 -0
- package/docs/wallet/DEVELOPING-STRATEGIES.md +713 -0
- package/docs/wallet/README.md +47 -0
- package/docs/wallet/STRATEGY.md +89 -0
- package/next.config.ts +21 -0
- package/package.json +151 -0
- package/postcss.config.mjs +8 -0
- package/prisma/migrations/20260214170000_baseline/migration.sql +511 -0
- package/prisma/migrations/20260216214537_add_passkey_model/migration.sql +18 -0
- package/prisma/migrations/20260217150500_add_credential_access_audit/migration.sql +31 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +447 -0
- package/public/logo-chevron.svg +31 -0
- package/public/logo-concentric.svg +31 -0
- package/public/logo-crosshatch.svg +39 -0
- package/public/logo-dashed.svg +39 -0
- package/public/logo-horizontal.svg +31 -0
- package/public/logo-m56.svg +64 -0
- package/public/logo.webp +0 -0
- package/scripts/add-app.js +245 -0
- package/scripts/init.sh +57 -0
- package/scripts/migrate-apikeys-to-credentials.ts +35 -0
- package/scripts/sandbox-agent-flow.sh +235 -0
- package/scripts/sandbox.sh +175 -0
- package/scripts/validate-job-docs.mjs +125 -0
- package/server/abi/SwapHelper.json +438 -0
- package/server/cli/approval.ts +447 -0
- package/server/cli/commands/app.ts +204 -0
- package/server/cli/commands/cron.ts +24 -0
- package/server/cli/commands/doctor.ts +1007 -0
- package/server/cli/commands/env.ts +456 -0
- package/server/cli/commands/init.ts +752 -0
- package/server/cli/commands/mcp.ts +125 -0
- package/server/cli/commands/restore.ts +314 -0
- package/server/cli/commands/shell-hook.ts +468 -0
- package/server/cli/commands/start.ts +62 -0
- package/server/cli/commands/status.ts +59 -0
- package/server/cli/commands/stop.ts +14 -0
- package/server/cli/commands/token.ts +180 -0
- package/server/cli/commands/unlock.ts +49 -0
- package/server/cli/commands/vault.ts +417 -0
- package/server/cli/index.ts +328 -0
- package/server/cli/lib/aura-parser.ts +64 -0
- package/server/cli/lib/credential-create.ts +74 -0
- package/server/cli/lib/credential-resolve.ts +254 -0
- package/server/cli/lib/dotenv-migrate.ts +116 -0
- package/server/cli/lib/dotenv-parser.ts +146 -0
- package/server/cli/lib/http.ts +91 -0
- package/server/cli/lib/init-steps.ts +76 -0
- package/server/cli/lib/local-agent-trust.ts +45 -0
- package/server/cli/lib/process.ts +136 -0
- package/server/cli/lib/prompt.ts +85 -0
- package/server/cli/lib/theme.ts +240 -0
- package/server/cli/socket.ts +570 -0
- package/server/cli/transport-client.ts +50 -0
- package/server/cron/index.ts +137 -0
- package/server/cron/job.ts +31 -0
- package/server/cron/jobs/balance-sync.ts +436 -0
- package/server/cron/jobs/incoming-scan.ts +506 -0
- package/server/cron/jobs/native-price.ts +70 -0
- package/server/cron/jobs/orphan-cleanup.ts +40 -0
- package/server/cron/jobs/strategy-runner.ts +175 -0
- package/server/cron/scheduler.ts +125 -0
- package/server/index.ts +406 -0
- package/server/lib/adapters/factory.ts +110 -0
- package/server/lib/adapters/index.ts +19 -0
- package/server/lib/adapters/router.ts +297 -0
- package/server/lib/adapters/telegram.ts +645 -0
- package/server/lib/adapters/types.ts +89 -0
- package/server/lib/adapters/webhook.ts +95 -0
- package/server/lib/address.ts +49 -0
- package/server/lib/agent-auth/contracts.ts +1194 -0
- package/server/lib/agent-profiles.ts +328 -0
- package/server/lib/ai.ts +285 -0
- package/server/lib/api-registry/contracts.ts +86 -0
- package/server/lib/api-registry/validation.ts +172 -0
- package/server/lib/apikey-migration.ts +189 -0
- package/server/lib/app-installer.ts +505 -0
- package/server/lib/app-tokens.ts +247 -0
- package/server/lib/auth.ts +314 -0
- package/server/lib/batch.ts +242 -0
- package/server/lib/cold.ts +874 -0
- package/server/lib/config.ts +381 -0
- package/server/lib/credential-access-audit.ts +85 -0
- package/server/lib/credential-access-policy.ts +110 -0
- package/server/lib/credential-health.ts +343 -0
- package/server/lib/credential-import.ts +487 -0
- package/server/lib/credential-scope.ts +87 -0
- package/server/lib/credential-shares.ts +190 -0
- package/server/lib/credential-transport.ts +342 -0
- package/server/lib/credential-vault.ts +77 -0
- package/server/lib/credentials.ts +333 -0
- package/server/lib/crypto.ts +8 -0
- package/server/lib/db.ts +15 -0
- package/server/lib/defaults.ts +366 -0
- package/server/lib/dex/index.ts +80 -0
- package/server/lib/dex/relay.ts +235 -0
- package/server/lib/dex/types.ts +59 -0
- package/server/lib/dex/uniswap.ts +370 -0
- package/server/lib/e2e-agent/artifacts.ts +36 -0
- package/server/lib/e2e-agent/contracts.ts +112 -0
- package/server/lib/e2e-agent/validation.ts +135 -0
- package/server/lib/encrypt.ts +128 -0
- package/server/lib/error.ts +20 -0
- package/server/lib/events.ts +205 -0
- package/server/lib/hot.ts +357 -0
- package/server/lib/key-fingerprint.ts +28 -0
- package/server/lib/logger.ts +331 -0
- package/server/lib/network.ts +137 -0
- package/server/lib/notifications.ts +219 -0
- package/server/lib/oauth2-refresh.ts +241 -0
- package/server/lib/oursecret.ts +54 -0
- package/server/lib/passkey-credential.ts +360 -0
- package/server/lib/passkey.ts +68 -0
- package/server/lib/permissions.ts +248 -0
- package/server/lib/pino.ts +24 -0
- package/server/lib/policy-preview.ts +138 -0
- package/server/lib/price.ts +338 -0
- package/server/lib/prices.ts +34 -0
- package/server/lib/project-scope.ts +239 -0
- package/server/lib/resolve-action.ts +427 -0
- package/server/lib/resolve.ts +36 -0
- package/server/lib/sessions.ts +632 -0
- package/server/lib/solana/connection.ts +26 -0
- package/server/lib/solana/jupiter.ts +128 -0
- package/server/lib/solana/transfer.ts +108 -0
- package/server/lib/solana/wallet.ts +136 -0
- package/server/lib/strategy/emits.ts +21 -0
- package/server/lib/strategy/engine.ts +1305 -0
- package/server/lib/strategy/executor.ts +115 -0
- package/server/lib/strategy/hook-context.ts +158 -0
- package/server/lib/strategy/hooks.ts +990 -0
- package/server/lib/strategy/index.ts +28 -0
- package/server/lib/strategy/installer.ts +305 -0
- package/server/lib/strategy/loader.ts +256 -0
- package/server/lib/strategy/message.ts +235 -0
- package/server/lib/strategy/repository.ts +218 -0
- package/server/lib/strategy/session-logger.ts +693 -0
- package/server/lib/strategy/sources.ts +288 -0
- package/server/lib/strategy/state.ts +189 -0
- package/server/lib/strategy/templates.ts +403 -0
- package/server/lib/strategy/tick.ts +404 -0
- package/server/lib/strategy/types.ts +230 -0
- package/server/lib/swap.ts +3 -0
- package/server/lib/temp.ts +86 -0
- package/server/lib/token-metadata.ts +86 -0
- package/server/lib/token-safety.ts +200 -0
- package/server/lib/token-search.ts +444 -0
- package/server/lib/totp.ts +194 -0
- package/server/lib/transactions.ts +123 -0
- package/server/lib/transport.ts +75 -0
- package/server/lib/txhistory/decoder.ts +262 -0
- package/server/lib/txhistory/enricher.ts +652 -0
- package/server/lib/txhistory/index.ts +391 -0
- package/server/lib/txhistory/signatures.ts +59 -0
- package/server/lib/verified-summary.ts +421 -0
- package/server/mcp/profile-policy.ts +30 -0
- package/server/mcp/server.ts +619 -0
- package/server/mcp/tools.ts +523 -0
- package/server/middleware/auth.ts +119 -0
- package/server/middleware/requestLogger.ts +84 -0
- package/server/routes/actions.ts +459 -0
- package/server/routes/adapters.ts +703 -0
- package/server/routes/addressbook.ts +113 -0
- package/server/routes/ai.ts +34 -0
- package/server/routes/apikeys.ts +295 -0
- package/server/routes/apps.ts +601 -0
- package/server/routes/auth.ts +457 -0
- package/server/routes/backup.ts +340 -0
- package/server/routes/batch.ts +270 -0
- package/server/routes/bookmarks.ts +162 -0
- package/server/routes/credential-shares.ts +198 -0
- package/server/routes/credential-vaults.ts +154 -0
- package/server/routes/credentials.ts +1290 -0
- package/server/routes/dashboard.ts +71 -0
- package/server/routes/defaults.ts +124 -0
- package/server/routes/fund.ts +229 -0
- package/server/routes/import.ts +352 -0
- package/server/routes/launch.ts +665 -0
- package/server/routes/lock.ts +54 -0
- package/server/routes/logs.ts +68 -0
- package/server/routes/nuke.ts +111 -0
- package/server/routes/passkey-credentials.ts +99 -0
- package/server/routes/passkey.ts +346 -0
- package/server/routes/portfolio.ts +217 -0
- package/server/routes/price.ts +63 -0
- package/server/routes/resolve.ts +31 -0
- package/server/routes/security.ts +45 -0
- package/server/routes/send-evm.ts +241 -0
- package/server/routes/send-solana.ts +281 -0
- package/server/routes/send.ts +178 -0
- package/server/routes/setup.ts +210 -0
- package/server/routes/strategy.ts +894 -0
- package/server/routes/swap-evm.ts +353 -0
- package/server/routes/swap-solana.ts +177 -0
- package/server/routes/swap.ts +356 -0
- package/server/routes/token.ts +247 -0
- package/server/routes/unlock.ts +403 -0
- package/server/routes/wallet-assets.ts +361 -0
- package/server/routes/wallet-transactions.ts +515 -0
- package/server/routes/wallet.ts +710 -0
- package/server/types.ts +146 -0
- package/skills/aurawallet/SKILL.md +739 -0
- package/skills/aurawallet-setup/SKILL.md +74 -0
- package/skills/security-review/SKILL.md +148 -0
- package/src/app/api/agent-requests/route.ts +30 -0
- package/src/app/api/apps/install/route.ts +126 -0
- package/src/app/api/apps/manifests/route.ts +16 -0
- package/src/app/api/apps/static/[...path]/route.ts +57 -0
- package/src/app/api/events/route.ts +92 -0
- package/src/app/api/page.tsx +212 -0
- package/src/app/api/workspace/[id]/apps/[wid]/route.ts +119 -0
- package/src/app/api/workspace/[id]/apps/route.ts +81 -0
- package/src/app/api/workspace/[id]/export/route.ts +67 -0
- package/src/app/api/workspace/[id]/route.ts +168 -0
- package/src/app/api/workspace/auth.ts +34 -0
- package/src/app/api/workspace/config/route.ts +106 -0
- package/src/app/api/workspace/import/route.ts +127 -0
- package/src/app/api/workspace/route.ts +116 -0
- package/src/app/app/page.tsx +2122 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/docs/page.tsx +178 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +572 -0
- package/src/app/health/page.tsx +5 -0
- package/src/app/hello/page.tsx +15 -0
- package/src/app/icon.png +0 -0
- package/src/app/layout.tsx +34 -0
- package/src/app/page.tsx +986 -0
- package/src/app/providers.tsx +90 -0
- package/src/app/share/[token]/page.tsx +295 -0
- package/src/components/ChainSelector.tsx +144 -0
- package/src/components/HumanActionBar.tsx +695 -0
- package/src/components/NotificationDrawer.tsx +129 -0
- package/src/components/apps/AgentKeysApp.tsx +490 -0
- package/src/components/apps/App.tsx +153 -0
- package/src/components/apps/AppGrid.tsx +15 -0
- package/src/components/apps/DetailedAddressDrawer.tsx +325 -0
- package/src/components/apps/DraggableApp.tsx +562 -0
- package/src/components/apps/IFrameApp.tsx +73 -0
- package/src/components/apps/LogsApp.tsx +360 -0
- package/src/components/apps/SendApp.tsx +394 -0
- package/src/components/apps/SetupWizardApp.tsx +1004 -0
- package/src/components/apps/SystemDefaultsApp.tsx +845 -0
- package/src/components/apps/ThirdPartyApp.tsx +428 -0
- package/src/components/apps/TokenApp.tsx +319 -0
- package/src/components/apps/TransactionsApp.tsx +438 -0
- package/src/components/apps/WalletDetailApp.tsx +1505 -0
- package/src/components/apps/index.ts +13 -0
- package/src/components/design-system/Button.tsx +53 -0
- package/src/components/design-system/ChainIndicator.tsx +65 -0
- package/src/components/design-system/ChainSelector.tsx +137 -0
- package/src/components/design-system/ConfirmationModal.tsx +106 -0
- package/src/components/design-system/ConfirmationPopover.tsx +81 -0
- package/src/components/design-system/Drawer.tsx +123 -0
- package/src/components/design-system/FilterDropdown.tsx +72 -0
- package/src/components/design-system/Modal.tsx +206 -0
- package/src/components/design-system/Popover.tsx +142 -0
- package/src/components/design-system/TextInput.tsx +85 -0
- package/src/components/design-system/Toggle.tsx +58 -0
- package/src/components/design-system/index.ts +11 -0
- package/src/components/docs/DocsThemeToggle.tsx +49 -0
- package/src/components/health/CredentialHealthDashboard.tsx +214 -0
- package/src/components/icons/ChainIcons.tsx +72 -0
- package/src/components/layout/AppStoreDrawer.tsx +369 -0
- package/src/components/layout/ContentArea.tsx +21 -0
- package/src/components/layout/TabBar.tsx +278 -0
- package/src/components/layout/WalletSidebar.tsx +1033 -0
- package/src/components/layout/index.ts +4 -0
- package/src/components/marketing/AuraWalletSpecOverlay.tsx +635 -0
- package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
- package/src/components/vault/ApiKeysConsole.tsx +1080 -0
- package/src/components/vault/AuditConsole.tsx +584 -0
- package/src/components/vault/CredentialDetail.tsx +455 -0
- package/src/components/vault/CredentialEmpty.tsx +55 -0
- package/src/components/vault/CredentialField.tsx +361 -0
- package/src/components/vault/CredentialForm.tsx +1212 -0
- package/src/components/vault/CredentialList.tsx +165 -0
- package/src/components/vault/CredentialRow.tsx +97 -0
- package/src/components/vault/CredentialShareModal.tsx +178 -0
- package/src/components/vault/CredentialVault.tsx +754 -0
- package/src/components/vault/CredentialWalletWidget.tsx +103 -0
- package/src/components/vault/ImportCredentialsModal.tsx +515 -0
- package/src/components/vault/LargeTypeModal.tsx +64 -0
- package/src/components/vault/PasswordGenerator.tsx +224 -0
- package/src/components/vault/TOTPDisplay.tsx +123 -0
- package/src/components/vault/VaultSidebar.tsx +413 -0
- package/src/components/vault/types.ts +54 -0
- package/src/context/AuthContext.tsx +337 -0
- package/src/context/PriceContext.tsx +113 -0
- package/src/context/ThemeContext.tsx +164 -0
- package/src/context/WebSocketContext.tsx +269 -0
- package/src/context/WorkspaceContext.tsx +668 -0
- package/src/hooks/index.ts +3 -0
- package/src/hooks/useAgentActions.ts +368 -0
- package/src/hooks/useBalance.ts +103 -0
- package/src/hooks/useBalances.ts +129 -0
- package/src/instrumentation.ts +12 -0
- package/src/lib/api.ts +449 -0
- package/src/lib/app-loader.ts +148 -0
- package/src/lib/app-registry.ts +178 -0
- package/src/lib/app-sdk.ts +157 -0
- package/src/lib/audit-console-adapter.ts +151 -0
- package/src/lib/auth-client.ts +75 -0
- package/src/lib/config.ts +74 -0
- package/src/lib/crypto.ts +112 -0
- package/src/lib/db.ts +21 -0
- package/src/lib/docs.ts +390 -0
- package/src/lib/events.ts +361 -0
- package/src/lib/pino.ts +24 -0
- package/src/lib/theme-handlers.ts +168 -0
- package/src/lib/theme.ts +351 -0
- package/src/lib/tokenData.ts +378 -0
- package/src/lib/vault-crypto.ts +129 -0
- package/src/lib/websocket-server.ts +302 -0
- package/src/lib/websocket-setup.ts +79 -0
- package/src/lib/wordlist.ts +2050 -0
- package/src/lib/workspace-handlers.ts +285 -0
- package/start.sh +80 -0
- package/tailwind.config.ts +99 -0
- package/tsconfig.json +42 -0
package/src/app/page.tsx
ADDED
|
@@ -0,0 +1,986 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
4
|
+
import Link from 'next/link';
|
|
5
|
+
import { Bot, ChevronDown, KeyRound } from 'lucide-react';
|
|
6
|
+
import { useAuth } from '@/context/AuthContext';
|
|
7
|
+
import { api, Api, unlockWallet, setupWallet, rekeySession, changePrimaryVaultPassword } from '@/lib/api';
|
|
8
|
+
import { generateVaultKeypair, getVaultPrivateKey } from '@/lib/vault-crypto';
|
|
9
|
+
import { CredentialVault } from '@/components/vault/CredentialVault';
|
|
10
|
+
import DocsThemeToggle from '@/components/docs/DocsThemeToggle';
|
|
11
|
+
import { Drawer } from '@/components/design-system/Drawer';
|
|
12
|
+
import { Modal } from '@/components/design-system/Modal';
|
|
13
|
+
|
|
14
|
+
interface VaultInfo {
|
|
15
|
+
id: string;
|
|
16
|
+
name?: string;
|
|
17
|
+
address: string;
|
|
18
|
+
solanaAddress?: string;
|
|
19
|
+
isUnlocked: boolean;
|
|
20
|
+
isPrimary: boolean;
|
|
21
|
+
createdAt: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface WalletData {
|
|
25
|
+
address: string;
|
|
26
|
+
tier: 'cold' | 'hot' | 'temp';
|
|
27
|
+
chain: string;
|
|
28
|
+
balance?: string;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type PageState = 'loading' | 'setup' | 'locked' | 'unlocked';
|
|
32
|
+
type LocalAgentMode = 'strict' | 'dev' | 'admin';
|
|
33
|
+
type SetupOnboardingStep = 'seed' | 'trust';
|
|
34
|
+
type LocalPolicySettings = {
|
|
35
|
+
profile: LocalAgentMode;
|
|
36
|
+
profileVersion: 'v1';
|
|
37
|
+
autoApprove: boolean;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const LOCAL_POLICY_PROFILES: LocalAgentMode[] = ['strict', 'dev', 'admin'];
|
|
41
|
+
|
|
42
|
+
type OnboardingSeedDraft = {
|
|
43
|
+
mnemonic: string;
|
|
44
|
+
createdAt: number;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const ONBOARDING_SEED_STORAGE_KEY = 'aura:onboarding-seed-draft';
|
|
48
|
+
const ONBOARDING_SEED_TTL_MS = 15 * 60 * 1000;
|
|
49
|
+
|
|
50
|
+
export default function UnlockPage() {
|
|
51
|
+
const { token, setToken, clearToken } = useAuth();
|
|
52
|
+
|
|
53
|
+
const [pageState, setPageState] = useState<PageState>('loading');
|
|
54
|
+
const [password, setPassword] = useState('');
|
|
55
|
+
const [loading, setLoading] = useState(false);
|
|
56
|
+
const [error, setError] = useState<string | null>(null);
|
|
57
|
+
const [mnemonic, setMnemonic] = useState<string | null>(null);
|
|
58
|
+
const [seedAcknowledged, setSeedAcknowledged] = useState(false);
|
|
59
|
+
const [seedRecoveryNotice, setSeedRecoveryNotice] = useState<string | null>(null);
|
|
60
|
+
const [localAgentMode, setLocalAgentMode] = useState<LocalAgentMode>('dev');
|
|
61
|
+
const [setupOnboardingStep, setSetupOnboardingStep] = useState<SetupOnboardingStep>('seed');
|
|
62
|
+
const [onboardingToken, setOnboardingToken] = useState<string | null>(null);
|
|
63
|
+
const [showSettingsDrawer, setShowSettingsDrawer] = useState(false);
|
|
64
|
+
const [policySettings, setPolicySettings] = useState<LocalPolicySettings | null>(null);
|
|
65
|
+
const [policyForm, setPolicyForm] = useState<LocalPolicySettings>({ profile: 'dev', profileVersion: 'v1', autoApprove: true });
|
|
66
|
+
const [policyLoadError, setPolicyLoadError] = useState<string | null>(null);
|
|
67
|
+
const [policySaveError, setPolicySaveError] = useState<string | null>(null);
|
|
68
|
+
const [policySaveSuccess, setPolicySaveSuccess] = useState<string | null>(null);
|
|
69
|
+
const [policyLoading, setPolicyLoading] = useState(false);
|
|
70
|
+
const [policySaving, setPolicySaving] = useState(false);
|
|
71
|
+
const [dangerConfirmOpen, setDangerConfirmOpen] = useState(false);
|
|
72
|
+
const [agentSettingsOpen, setAgentSettingsOpen] = useState(false);
|
|
73
|
+
const [securitySettingsOpen, setSecuritySettingsOpen] = useState(false);
|
|
74
|
+
const [showPasswordModal, setShowPasswordModal] = useState(false);
|
|
75
|
+
const [currentPasswordValue, setCurrentPasswordValue] = useState('');
|
|
76
|
+
const [newPasswordValue, setNewPasswordValue] = useState('');
|
|
77
|
+
const [confirmPasswordValue, setConfirmPasswordValue] = useState('');
|
|
78
|
+
const [passwordChangeError, setPasswordChangeError] = useState<string | null>(null);
|
|
79
|
+
const [passwordChangeSuccess, setPasswordChangeSuccess] = useState<string | null>(null);
|
|
80
|
+
const [passwordChanging, setPasswordChanging] = useState(false);
|
|
81
|
+
|
|
82
|
+
const hasPendingSeedConfirmation = useMemo(() => Boolean(mnemonic), [mnemonic]);
|
|
83
|
+
|
|
84
|
+
useEffect(() => {
|
|
85
|
+
try {
|
|
86
|
+
const rawDraft = sessionStorage.getItem(ONBOARDING_SEED_STORAGE_KEY);
|
|
87
|
+
if (!rawDraft) return;
|
|
88
|
+
const parsed = JSON.parse(rawDraft) as OnboardingSeedDraft;
|
|
89
|
+
if (!parsed?.mnemonic || typeof parsed.createdAt !== 'number') {
|
|
90
|
+
sessionStorage.removeItem(ONBOARDING_SEED_STORAGE_KEY);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (Date.now() - parsed.createdAt > ONBOARDING_SEED_TTL_MS) {
|
|
94
|
+
sessionStorage.removeItem(ONBOARDING_SEED_STORAGE_KEY);
|
|
95
|
+
setSeedRecoveryNotice('Recovery phrase draft expired. Restart setup to generate a new phrase.');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
setMnemonic(parsed.mnemonic);
|
|
100
|
+
setSetupOnboardingStep('seed');
|
|
101
|
+
setPageState('setup');
|
|
102
|
+
setSeedRecoveryNotice('Recovered your in-progress recovery phrase for this tab. Confirm after you store it safely.');
|
|
103
|
+
} catch {
|
|
104
|
+
sessionStorage.removeItem(ONBOARDING_SEED_STORAGE_KEY);
|
|
105
|
+
}
|
|
106
|
+
}, []);
|
|
107
|
+
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!mnemonic) {
|
|
110
|
+
sessionStorage.removeItem(ONBOARDING_SEED_STORAGE_KEY);
|
|
111
|
+
setSetupOnboardingStep('seed');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const draft: OnboardingSeedDraft = {
|
|
115
|
+
mnemonic,
|
|
116
|
+
createdAt: Date.now(),
|
|
117
|
+
};
|
|
118
|
+
sessionStorage.setItem(ONBOARDING_SEED_STORAGE_KEY, JSON.stringify(draft));
|
|
119
|
+
}, [mnemonic]);
|
|
120
|
+
|
|
121
|
+
const fetchState = useCallback(async () => {
|
|
122
|
+
// Page reload recovery: keypair is memory-only and lost on reload.
|
|
123
|
+
// If token exists but keypair is gone, regenerate keypair and re-key the session.
|
|
124
|
+
if (token && !getVaultPrivateKey()) {
|
|
125
|
+
try {
|
|
126
|
+
const { publicKeyBase64 } = await generateVaultKeypair();
|
|
127
|
+
const result = await rekeySession(publicKeyBase64);
|
|
128
|
+
if (result.token) {
|
|
129
|
+
setToken(result.token);
|
|
130
|
+
}
|
|
131
|
+
// Keypair restored, continue to fetch state normally
|
|
132
|
+
} catch {
|
|
133
|
+
// Re-key failed (token expired, server restarted, vault locked) — fall back to locked
|
|
134
|
+
clearToken();
|
|
135
|
+
setPageState('locked');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const data = await api.get<{ wallets: WalletData[]; unlocked: boolean; vaults: VaultInfo[] }>(
|
|
142
|
+
Api.Wallet,
|
|
143
|
+
'/wallets',
|
|
144
|
+
{ includeHidden: true }
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const configured = data.wallets.some(w => w.tier === 'cold') || (data.vaults && data.vaults.some(v => v.isPrimary));
|
|
148
|
+
|
|
149
|
+
if (!configured) {
|
|
150
|
+
setPageState('setup');
|
|
151
|
+
} else if (hasPendingSeedConfirmation) {
|
|
152
|
+
setPageState('setup');
|
|
153
|
+
} else if (!data.unlocked || !token) {
|
|
154
|
+
setPageState('locked');
|
|
155
|
+
} else {
|
|
156
|
+
setPageState('unlocked');
|
|
157
|
+
}
|
|
158
|
+
} catch {
|
|
159
|
+
setPageState('setup');
|
|
160
|
+
}
|
|
161
|
+
}, [token, clearToken, hasPendingSeedConfirmation]);
|
|
162
|
+
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
fetchState();
|
|
165
|
+
}, [fetchState]);
|
|
166
|
+
|
|
167
|
+
const loadLocalPolicySettings = useCallback(async () => {
|
|
168
|
+
if (!token) {
|
|
169
|
+
setPolicyLoadError('Unlock vault first to manage local socket policy.');
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
setPolicyLoading(true);
|
|
174
|
+
setPolicyLoadError(null);
|
|
175
|
+
setPolicySaveError(null);
|
|
176
|
+
setPolicySaveSuccess(null);
|
|
177
|
+
try {
|
|
178
|
+
const headers = { Authorization: `Bearer ${token}` };
|
|
179
|
+
const baseUrl = api.getBaseUrl(Api.Wallet);
|
|
180
|
+
const defaultsRes = await fetch(`${baseUrl}/defaults`, { headers });
|
|
181
|
+
if (!defaultsRes.ok) {
|
|
182
|
+
throw new Error('Failed to load canonical trust policy defaults.');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const defaultsJson = await defaultsRes.json() as {
|
|
186
|
+
success?: boolean;
|
|
187
|
+
defaults?: Record<string, Array<{ key: string; value: unknown }>>;
|
|
188
|
+
};
|
|
189
|
+
if (defaultsJson.success === false) {
|
|
190
|
+
throw new Error('Failed to load canonical trust policy defaults.');
|
|
191
|
+
}
|
|
192
|
+
const flatDefaults = Object.values(defaultsJson.defaults || {}).flat();
|
|
193
|
+
const findDefault = (key: string): unknown => flatDefaults.find((item) => item.key === key)?.value;
|
|
194
|
+
|
|
195
|
+
const loadedProfile = String(findDefault('trust.localProfile') ?? '').trim() as LocalAgentMode;
|
|
196
|
+
if (!LOCAL_POLICY_PROFILES.includes(loadedProfile)) {
|
|
197
|
+
throw new Error(`Unknown persisted local profile: ${loadedProfile || '(empty)'}`);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const loaded: LocalPolicySettings = {
|
|
201
|
+
profile: loadedProfile,
|
|
202
|
+
profileVersion: 'v1',
|
|
203
|
+
autoApprove: Boolean(findDefault('trust.localAutoApprove')),
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
if (String(findDefault('trust.localProfileVersion') ?? 'v1').trim() !== 'v1') {
|
|
207
|
+
throw new Error('Unknown local profile version; refusing to edit settings.');
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
setPolicySettings(loaded);
|
|
211
|
+
setPolicyForm(loaded);
|
|
212
|
+
} catch (err) {
|
|
213
|
+
setPolicyLoadError((err as Error).message || 'Failed to load policy settings');
|
|
214
|
+
setPolicySettings(null);
|
|
215
|
+
} finally {
|
|
216
|
+
setPolicyLoading(false);
|
|
217
|
+
}
|
|
218
|
+
}, [token]);
|
|
219
|
+
|
|
220
|
+
useEffect(() => {
|
|
221
|
+
if (showSettingsDrawer) {
|
|
222
|
+
void loadLocalPolicySettings();
|
|
223
|
+
}
|
|
224
|
+
}, [showSettingsDrawer, loadLocalPolicySettings]);
|
|
225
|
+
|
|
226
|
+
const persistLocalPolicySettings = useCallback(async () => {
|
|
227
|
+
if (!token) throw new Error('Missing auth token for save.');
|
|
228
|
+
if (!LOCAL_POLICY_PROFILES.includes(policyForm.profile)) {
|
|
229
|
+
throw new Error('Unknown profile selected; refusing to persist.');
|
|
230
|
+
}
|
|
231
|
+
const baseUrl = api.getBaseUrl(Api.Wallet);
|
|
232
|
+
const headers = {
|
|
233
|
+
'Content-Type': 'application/json',
|
|
234
|
+
Authorization: `Bearer ${token}`,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const profileRes = await fetch(`${baseUrl}/defaults/trust.localProfile`, {
|
|
238
|
+
method: 'PATCH',
|
|
239
|
+
headers,
|
|
240
|
+
body: JSON.stringify({ value: policyForm.profile }),
|
|
241
|
+
});
|
|
242
|
+
const profileVersionRes = await fetch(`${baseUrl}/defaults/trust.localProfileVersion`, {
|
|
243
|
+
method: 'PATCH',
|
|
244
|
+
headers,
|
|
245
|
+
body: JSON.stringify({ value: 'v1' }),
|
|
246
|
+
});
|
|
247
|
+
const autoApproveRes = await fetch(`${baseUrl}/defaults/trust.localAutoApprove`, {
|
|
248
|
+
method: 'PATCH',
|
|
249
|
+
headers,
|
|
250
|
+
body: JSON.stringify({ value: policyForm.autoApprove }),
|
|
251
|
+
});
|
|
252
|
+
if (!profileRes.ok || !profileVersionRes.ok || !autoApproveRes.ok) {
|
|
253
|
+
throw new Error('Failed to save canonical trust policy defaults.');
|
|
254
|
+
}
|
|
255
|
+
}, [policyForm, token]);
|
|
256
|
+
|
|
257
|
+
const handleSaveLocalPolicy = useCallback(async () => {
|
|
258
|
+
if (!policySettings || policyLoading || policySaving) return;
|
|
259
|
+
const enablingDangerous = policySettings.profile !== 'admin' && policyForm.profile === 'admin';
|
|
260
|
+
if (enablingDangerous && !dangerConfirmOpen) {
|
|
261
|
+
setDangerConfirmOpen(true);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
setPolicySaving(true);
|
|
266
|
+
setPolicySaveError(null);
|
|
267
|
+
setPolicySaveSuccess(null);
|
|
268
|
+
try {
|
|
269
|
+
await persistLocalPolicySettings();
|
|
270
|
+
const saved = { ...policyForm, profileVersion: 'v1' as const };
|
|
271
|
+
setPolicySettings(saved);
|
|
272
|
+
setPolicyForm(saved);
|
|
273
|
+
setDangerConfirmOpen(false);
|
|
274
|
+
setPolicySaveSuccess('Local trust policy saved.');
|
|
275
|
+
} catch (err) {
|
|
276
|
+
setPolicySaveError((err as Error).message || 'Failed to save policy settings');
|
|
277
|
+
if (policySettings) {
|
|
278
|
+
setPolicyForm(policySettings);
|
|
279
|
+
}
|
|
280
|
+
setDangerConfirmOpen(false);
|
|
281
|
+
} finally {
|
|
282
|
+
setPolicySaving(false);
|
|
283
|
+
}
|
|
284
|
+
}, [dangerConfirmOpen, persistLocalPolicySettings, policyForm, policyLoading, policySaving, policySettings]);
|
|
285
|
+
|
|
286
|
+
const closePasswordModal = useCallback(() => {
|
|
287
|
+
setShowPasswordModal(false);
|
|
288
|
+
setCurrentPasswordValue('');
|
|
289
|
+
setNewPasswordValue('');
|
|
290
|
+
setConfirmPasswordValue('');
|
|
291
|
+
setPasswordChangeError(null);
|
|
292
|
+
setPasswordChanging(false);
|
|
293
|
+
}, []);
|
|
294
|
+
|
|
295
|
+
const handleChangePrimaryPassword = useCallback(async (e: React.FormEvent) => {
|
|
296
|
+
e.preventDefault();
|
|
297
|
+
setPasswordChangeError(null);
|
|
298
|
+
setPasswordChangeSuccess(null);
|
|
299
|
+
|
|
300
|
+
if (newPasswordValue.length < 8) {
|
|
301
|
+
setPasswordChangeError('New password must be at least 8 characters.');
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (newPasswordValue !== confirmPasswordValue) {
|
|
305
|
+
setPasswordChangeError('New password and confirmation do not match.');
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
setPasswordChanging(true);
|
|
310
|
+
try {
|
|
311
|
+
await changePrimaryVaultPassword(currentPasswordValue, newPasswordValue);
|
|
312
|
+
setPasswordChangeSuccess('Primary vault password updated.');
|
|
313
|
+
closePasswordModal();
|
|
314
|
+
} catch (err) {
|
|
315
|
+
setPasswordChangeError((err as Error).message || 'Failed to change primary password.');
|
|
316
|
+
} finally {
|
|
317
|
+
setPasswordChanging(false);
|
|
318
|
+
}
|
|
319
|
+
}, [closePasswordModal, confirmPasswordValue, currentPasswordValue, newPasswordValue]);
|
|
320
|
+
|
|
321
|
+
const handleUnlock = async (e: React.FormEvent) => {
|
|
322
|
+
e.preventDefault();
|
|
323
|
+
if (!password) return;
|
|
324
|
+
|
|
325
|
+
setLoading(true);
|
|
326
|
+
setError(null);
|
|
327
|
+
try {
|
|
328
|
+
// Generate keypair before unlock so the token is minted with our pubkey
|
|
329
|
+
const { publicKeyBase64 } = await generateVaultKeypair();
|
|
330
|
+
const data = await unlockWallet(password, undefined, publicKeyBase64);
|
|
331
|
+
if (data.token) {
|
|
332
|
+
setToken(data.token);
|
|
333
|
+
}
|
|
334
|
+
setPassword('');
|
|
335
|
+
fetchState();
|
|
336
|
+
} catch (err) {
|
|
337
|
+
setError((err as Error).message || 'Unlock failed');
|
|
338
|
+
} finally {
|
|
339
|
+
setLoading(false);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
const handleSetup = async (e: React.FormEvent) => {
|
|
344
|
+
e.preventDefault();
|
|
345
|
+
if (password.length < 8) return;
|
|
346
|
+
|
|
347
|
+
setLoading(true);
|
|
348
|
+
setError(null);
|
|
349
|
+
try {
|
|
350
|
+
// Generate keypair before setup so the initial token has our pubkey
|
|
351
|
+
const { publicKeyBase64 } = await generateVaultKeypair();
|
|
352
|
+
const result = await setupWallet(password, publicKeyBase64);
|
|
353
|
+
if (result.token) {
|
|
354
|
+
setToken(result.token);
|
|
355
|
+
setOnboardingToken(result.token);
|
|
356
|
+
}
|
|
357
|
+
if (result.mnemonic) {
|
|
358
|
+
setMnemonic(result.mnemonic);
|
|
359
|
+
setSeedAcknowledged(false);
|
|
360
|
+
setSetupOnboardingStep('seed');
|
|
361
|
+
setSeedRecoveryNotice(null);
|
|
362
|
+
}
|
|
363
|
+
setPassword('');
|
|
364
|
+
if (!result.mnemonic) fetchState();
|
|
365
|
+
} catch (err) {
|
|
366
|
+
setError((err as Error).message || 'Setup failed');
|
|
367
|
+
} finally {
|
|
368
|
+
setLoading(false);
|
|
369
|
+
}
|
|
370
|
+
};
|
|
371
|
+
|
|
372
|
+
const persistLocalAgentMode = useCallback(async () => {
|
|
373
|
+
const authToken = onboardingToken || token;
|
|
374
|
+
if (!authToken) {
|
|
375
|
+
throw new Error('Session token unavailable. Unlock again and retry setup.');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const profile = localAgentMode;
|
|
379
|
+
const profileVersion = 'v1';
|
|
380
|
+
const autoApprove = profile !== 'strict';
|
|
381
|
+
const baseUrl = api.getBaseUrl(Api.Wallet);
|
|
382
|
+
const headers = {
|
|
383
|
+
'Content-Type': 'application/json',
|
|
384
|
+
'Authorization': `Bearer ${authToken}`,
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
await fetch(`${baseUrl}/defaults/trust.localProfile`, {
|
|
388
|
+
method: 'PATCH',
|
|
389
|
+
headers,
|
|
390
|
+
body: JSON.stringify({ value: profile }),
|
|
391
|
+
});
|
|
392
|
+
await fetch(`${baseUrl}/defaults/trust.localProfileVersion`, {
|
|
393
|
+
method: 'PATCH',
|
|
394
|
+
headers,
|
|
395
|
+
body: JSON.stringify({ value: profileVersion }),
|
|
396
|
+
});
|
|
397
|
+
await fetch(`${baseUrl}/defaults/trust.localAutoApprove`, {
|
|
398
|
+
method: 'PATCH',
|
|
399
|
+
headers,
|
|
400
|
+
body: JSON.stringify({ value: autoApprove }),
|
|
401
|
+
});
|
|
402
|
+
}, [localAgentMode, onboardingToken, token]);
|
|
403
|
+
|
|
404
|
+
const handleFinalizeOnboarding = useCallback(async () => {
|
|
405
|
+
setLoading(true);
|
|
406
|
+
setError(null);
|
|
407
|
+
try {
|
|
408
|
+
await persistLocalAgentMode();
|
|
409
|
+
setMnemonic(null);
|
|
410
|
+
setSeedAcknowledged(false);
|
|
411
|
+
setSeedRecoveryNotice(null);
|
|
412
|
+
setOnboardingToken(null);
|
|
413
|
+
setSetupOnboardingStep('seed');
|
|
414
|
+
fetchState();
|
|
415
|
+
} catch (err) {
|
|
416
|
+
setError((err as Error).message || 'Failed to save local agent mode');
|
|
417
|
+
} finally {
|
|
418
|
+
setLoading(false);
|
|
419
|
+
}
|
|
420
|
+
}, [fetchState, persistLocalAgentMode]);
|
|
421
|
+
|
|
422
|
+
const handleLock = useCallback(() => {
|
|
423
|
+
setPageState('locked');
|
|
424
|
+
}, []);
|
|
425
|
+
|
|
426
|
+
// Unlocked: render full-screen vault + root settings drawer controls
|
|
427
|
+
if (pageState === 'unlocked') {
|
|
428
|
+
return (
|
|
429
|
+
<div className="relative h-screen">
|
|
430
|
+
<CredentialVault onLock={handleLock} onSettings={() => setShowSettingsDrawer(true)} />
|
|
431
|
+
|
|
432
|
+
<Drawer
|
|
433
|
+
isOpen={showSettingsDrawer}
|
|
434
|
+
onClose={() => {
|
|
435
|
+
setShowSettingsDrawer(false);
|
|
436
|
+
setDangerConfirmOpen(false);
|
|
437
|
+
setPolicySaveError(null);
|
|
438
|
+
setPasswordChangeError(null);
|
|
439
|
+
setAgentSettingsOpen(false);
|
|
440
|
+
setSecuritySettingsOpen(false);
|
|
441
|
+
setShowPasswordModal(false);
|
|
442
|
+
if (policySettings) setPolicyForm(policySettings);
|
|
443
|
+
}}
|
|
444
|
+
title="ROOT SETTINGS"
|
|
445
|
+
subtitle="Local socket policy"
|
|
446
|
+
>
|
|
447
|
+
<div className="space-y-4">
|
|
448
|
+
{passwordChangeSuccess && (
|
|
449
|
+
<div className="text-[10px] text-[var(--color-info,#0047ff)] border border-[var(--color-info,#0047ff)]/30 bg-[var(--color-info,#0047ff)]/10 px-3 py-2">
|
|
450
|
+
{passwordChangeSuccess}
|
|
451
|
+
</div>
|
|
452
|
+
)}
|
|
453
|
+
|
|
454
|
+
<div className="bg-[var(--color-surface,#fff)] border border-[var(--color-border,#d4d4d8)]">
|
|
455
|
+
<button
|
|
456
|
+
type="button"
|
|
457
|
+
onClick={() => setAgentSettingsOpen((open) => !open)}
|
|
458
|
+
className="w-full p-4 flex items-center justify-between hover:bg-[var(--color-surface-alt,#fafafa)] transition-colors"
|
|
459
|
+
>
|
|
460
|
+
<div className="flex items-center gap-2">
|
|
461
|
+
<Bot size={12} className="text-[var(--color-text-muted,#6b7280)]" />
|
|
462
|
+
<span className="font-mono text-[10px] tracking-widest text-[var(--color-text-muted,#6b7280)]">DEFAULT_AGENT_PROFILE</span>
|
|
463
|
+
</div>
|
|
464
|
+
<ChevronDown size={12} className={`text-[var(--color-text-muted,#6b7280)] transition-transform ${agentSettingsOpen ? 'rotate-180' : ''}`} />
|
|
465
|
+
</button>
|
|
466
|
+
{agentSettingsOpen && (
|
|
467
|
+
<div className="px-4 pb-4 space-y-3 border-t border-[var(--color-border,#d4d4d8)]">
|
|
468
|
+
{policyLoadError && (
|
|
469
|
+
<div className="space-y-2 pt-3">
|
|
470
|
+
<div className="text-[10px] text-[var(--color-danger,#ef4444)] border border-[var(--color-danger,#ef4444)]/30 bg-[var(--color-danger,#ef4444)]/10 px-3 py-2">
|
|
471
|
+
{policyLoadError}
|
|
472
|
+
</div>
|
|
473
|
+
<button
|
|
474
|
+
type="button"
|
|
475
|
+
onClick={() => void loadLocalPolicySettings()}
|
|
476
|
+
disabled={policyLoading}
|
|
477
|
+
className="h-9 px-3 border border-[var(--color-border,#d4d4d8)] font-mono text-[10px] tracking-widest"
|
|
478
|
+
>
|
|
479
|
+
{policyLoading ? 'RETRYING...' : 'RETRY LOAD'}
|
|
480
|
+
</button>
|
|
481
|
+
</div>
|
|
482
|
+
)}
|
|
483
|
+
|
|
484
|
+
{!policyLoadError && (
|
|
485
|
+
<>
|
|
486
|
+
<label className="pt-3 flex items-center justify-between text-[10px] font-mono text-[var(--color-text,#0a0a0a)]">
|
|
487
|
+
<span>AUTO-APPROVE LOCAL REQUESTS</span>
|
|
488
|
+
<input
|
|
489
|
+
type="checkbox"
|
|
490
|
+
checked={policyForm.autoApprove}
|
|
491
|
+
onChange={(e) => setPolicyForm((prev) => ({ ...prev, autoApprove: e.target.checked }))}
|
|
492
|
+
disabled={policyLoading || policySaving}
|
|
493
|
+
/>
|
|
494
|
+
</label>
|
|
495
|
+
|
|
496
|
+
<div>
|
|
497
|
+
<label className="block text-[9px] text-[var(--color-text-faint,#9ca3af)] tracking-widest mb-2">LOCAL PROFILE</label>
|
|
498
|
+
<select
|
|
499
|
+
aria-label="LOCAL PROFILE"
|
|
500
|
+
value={policyForm.profile}
|
|
501
|
+
onChange={(e) => setPolicyForm((prev) => ({ ...prev, profile: e.target.value as LocalAgentMode }))}
|
|
502
|
+
disabled={policyLoading || policySaving}
|
|
503
|
+
className="w-full h-10 px-2 border border-[var(--color-border,#d4d4d8)] bg-[var(--color-surface,#fff)] text-[11px] font-mono"
|
|
504
|
+
>
|
|
505
|
+
<option value="strict">strict</option>
|
|
506
|
+
<option value="dev">dev</option>
|
|
507
|
+
<option value="admin">admin (dangerous)</option>
|
|
508
|
+
</select>
|
|
509
|
+
</div>
|
|
510
|
+
|
|
511
|
+
{dangerConfirmOpen && (
|
|
512
|
+
<div className="text-[10px] text-[var(--color-danger,#ef4444)] border border-[var(--color-danger,#ef4444)]/30 bg-[var(--color-danger,#ef4444)]/10 px-3 py-2 space-y-2">
|
|
513
|
+
<div>Enabling admin mode is dangerous and broadens token scope.</div>
|
|
514
|
+
<div className="flex gap-2">
|
|
515
|
+
<button
|
|
516
|
+
type="button"
|
|
517
|
+
onClick={() => {
|
|
518
|
+
setDangerConfirmOpen(false);
|
|
519
|
+
if (policySettings) setPolicyForm(policySettings);
|
|
520
|
+
}}
|
|
521
|
+
className="h-8 px-3 border border-[var(--color-border,#d4d4d8)] text-[10px] font-mono"
|
|
522
|
+
>
|
|
523
|
+
CANCEL
|
|
524
|
+
</button>
|
|
525
|
+
<button
|
|
526
|
+
type="button"
|
|
527
|
+
onClick={() => { void handleSaveLocalPolicy(); }}
|
|
528
|
+
disabled={policySaving}
|
|
529
|
+
className="h-8 px-3 bg-[var(--color-danger,#ef4444)] text-white text-[10px] font-mono"
|
|
530
|
+
>
|
|
531
|
+
CONFIRM ADMIN MODE
|
|
532
|
+
</button>
|
|
533
|
+
</div>
|
|
534
|
+
</div>
|
|
535
|
+
)}
|
|
536
|
+
|
|
537
|
+
{policySaveError && <div className="text-[10px] text-[var(--color-danger,#ef4444)]">{policySaveError}</div>}
|
|
538
|
+
{policySaveSuccess && <div className="text-[10px] text-[var(--color-info,#0047ff)]">{policySaveSuccess}</div>}
|
|
539
|
+
|
|
540
|
+
<button
|
|
541
|
+
type="button"
|
|
542
|
+
onClick={() => { void handleSaveLocalPolicy(); }}
|
|
543
|
+
disabled={Boolean(policyLoadError) || policyLoading || policySaving || !policySettings}
|
|
544
|
+
className="w-full h-10 bg-[var(--color-text,#0a0a0a)] text-white text-[10px] font-mono tracking-widest disabled:opacity-40"
|
|
545
|
+
>
|
|
546
|
+
{policySaving ? 'SAVING...' : 'SAVE LOCAL POLICY'}
|
|
547
|
+
</button>
|
|
548
|
+
</>
|
|
549
|
+
)}
|
|
550
|
+
</div>
|
|
551
|
+
)}
|
|
552
|
+
</div>
|
|
553
|
+
|
|
554
|
+
<div className="bg-[var(--color-surface,#fff)] border border-[var(--color-border,#d4d4d8)]">
|
|
555
|
+
<button
|
|
556
|
+
type="button"
|
|
557
|
+
onClick={() => setSecuritySettingsOpen((open) => !open)}
|
|
558
|
+
className="w-full p-4 flex items-center justify-between hover:bg-[var(--color-surface-alt,#fafafa)] transition-colors"
|
|
559
|
+
>
|
|
560
|
+
<div className="flex items-center gap-2">
|
|
561
|
+
<KeyRound size={12} className="text-[var(--color-text-muted,#6b7280)]" />
|
|
562
|
+
<span className="font-mono text-[10px] tracking-widest text-[var(--color-text-muted,#6b7280)]">PRIMARY_PASSWORD</span>
|
|
563
|
+
</div>
|
|
564
|
+
<ChevronDown size={12} className={`text-[var(--color-text-muted,#6b7280)] transition-transform ${securitySettingsOpen ? 'rotate-180' : ''}`} />
|
|
565
|
+
</button>
|
|
566
|
+
{securitySettingsOpen && (
|
|
567
|
+
<div className="px-4 pb-4 pt-3 space-y-3 border-t border-[var(--color-border,#d4d4d8)]">
|
|
568
|
+
<div className="text-[9px] text-[var(--color-text-muted,#6b7280)] leading-relaxed">
|
|
569
|
+
Rotate your primary vault password. This updates the vault wrapper encryption and keeps existing credentials intact.
|
|
570
|
+
</div>
|
|
571
|
+
{passwordChangeError && (
|
|
572
|
+
<div className="text-[10px] text-[var(--color-danger,#ef4444)] border border-[var(--color-danger,#ef4444)]/30 bg-[var(--color-danger,#ef4444)]/10 px-3 py-2">
|
|
573
|
+
{passwordChangeError}
|
|
574
|
+
</div>
|
|
575
|
+
)}
|
|
576
|
+
<button
|
|
577
|
+
type="button"
|
|
578
|
+
onClick={() => {
|
|
579
|
+
setPasswordChangeError(null);
|
|
580
|
+
setShowPasswordModal(true);
|
|
581
|
+
}}
|
|
582
|
+
className="w-full h-10 border border-[var(--color-border,#d4d4d8)] bg-[var(--color-surface,#fff)] text-[10px] font-mono tracking-widest hover:border-[var(--color-text,#0a0a0a)]"
|
|
583
|
+
>
|
|
584
|
+
CHANGE PRIMARY PASSWORD
|
|
585
|
+
</button>
|
|
586
|
+
</div>
|
|
587
|
+
)}
|
|
588
|
+
</div>
|
|
589
|
+
</div>
|
|
590
|
+
</Drawer>
|
|
591
|
+
|
|
592
|
+
<Modal
|
|
593
|
+
isOpen={showPasswordModal}
|
|
594
|
+
onClose={closePasswordModal}
|
|
595
|
+
title="Change Primary Password"
|
|
596
|
+
subtitle="Security"
|
|
597
|
+
size="sm"
|
|
598
|
+
>
|
|
599
|
+
<form onSubmit={handleChangePrimaryPassword} className="space-y-3">
|
|
600
|
+
<div>
|
|
601
|
+
<label className="block text-[9px] text-[var(--color-text-faint,#9ca3af)] tracking-widest mb-2">CURRENT PASSWORD</label>
|
|
602
|
+
<input
|
|
603
|
+
type="password"
|
|
604
|
+
aria-label="CURRENT PASSWORD"
|
|
605
|
+
value={currentPasswordValue}
|
|
606
|
+
onChange={(e) => setCurrentPasswordValue(e.target.value)}
|
|
607
|
+
autoFocus
|
|
608
|
+
className="w-full h-10 px-3 border border-[var(--color-border,#d4d4d8)] bg-[var(--color-surface,#fff)] text-[11px] font-mono"
|
|
609
|
+
/>
|
|
610
|
+
</div>
|
|
611
|
+
<div>
|
|
612
|
+
<label className="block text-[9px] text-[var(--color-text-faint,#9ca3af)] tracking-widest mb-2">NEW PASSWORD</label>
|
|
613
|
+
<input
|
|
614
|
+
type="password"
|
|
615
|
+
aria-label="NEW PASSWORD"
|
|
616
|
+
value={newPasswordValue}
|
|
617
|
+
onChange={(e) => setNewPasswordValue(e.target.value)}
|
|
618
|
+
className="w-full h-10 px-3 border border-[var(--color-border,#d4d4d8)] bg-[var(--color-surface,#fff)] text-[11px] font-mono"
|
|
619
|
+
/>
|
|
620
|
+
</div>
|
|
621
|
+
<div>
|
|
622
|
+
<label className="block text-[9px] text-[var(--color-text-faint,#9ca3af)] tracking-widest mb-2">CONFIRM NEW PASSWORD</label>
|
|
623
|
+
<input
|
|
624
|
+
type="password"
|
|
625
|
+
aria-label="CONFIRM NEW PASSWORD"
|
|
626
|
+
value={confirmPasswordValue}
|
|
627
|
+
onChange={(e) => setConfirmPasswordValue(e.target.value)}
|
|
628
|
+
className="w-full h-10 px-3 border border-[var(--color-border,#d4d4d8)] bg-[var(--color-surface,#fff)] text-[11px] font-mono"
|
|
629
|
+
/>
|
|
630
|
+
</div>
|
|
631
|
+
{passwordChangeError && <div className="text-[10px] text-[var(--color-danger,#ef4444)]">{passwordChangeError}</div>}
|
|
632
|
+
<div className="flex gap-2 pt-2">
|
|
633
|
+
<button
|
|
634
|
+
type="button"
|
|
635
|
+
onClick={closePasswordModal}
|
|
636
|
+
disabled={passwordChanging}
|
|
637
|
+
className="flex-1 h-10 border border-[var(--color-border,#d4d4d8)] text-[10px] font-mono tracking-widest"
|
|
638
|
+
>
|
|
639
|
+
CANCEL
|
|
640
|
+
</button>
|
|
641
|
+
<button
|
|
642
|
+
type="submit"
|
|
643
|
+
disabled={passwordChanging || !currentPasswordValue || !newPasswordValue || !confirmPasswordValue}
|
|
644
|
+
className="flex-1 h-10 bg-[var(--color-text,#0a0a0a)] text-white text-[10px] font-mono tracking-widest disabled:opacity-40"
|
|
645
|
+
>
|
|
646
|
+
{passwordChanging ? 'UPDATING...' : 'UPDATE PASSWORD'}
|
|
647
|
+
</button>
|
|
648
|
+
</div>
|
|
649
|
+
</form>
|
|
650
|
+
</Modal>
|
|
651
|
+
</div>
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
return (
|
|
656
|
+
<div className="min-h-screen bg-[var(--color-background,#f4f4f5)] relative flex items-center justify-center p-4">
|
|
657
|
+
{/* Background — sterile field (same as docs/api) */}
|
|
658
|
+
<div className="fixed inset-0 pointer-events-none z-0 overflow-hidden">
|
|
659
|
+
<div className="absolute inset-0 bg-grid-adaptive bg-[size:4rem_4rem] opacity-30" />
|
|
660
|
+
<div className="absolute inset-0 tyvek-texture opacity-40 mix-blend-multiply" />
|
|
661
|
+
|
|
662
|
+
{/* Giant background typography */}
|
|
663
|
+
<div className="absolute top-[5%] left-[5%] opacity-5 select-none">
|
|
664
|
+
<h1 className="text-[15vw] font-bold leading-none text-[var(--color-text,#0a0a0a)] font-mono tracking-tighter">
|
|
665
|
+
AURA
|
|
666
|
+
</h1>
|
|
667
|
+
</div>
|
|
668
|
+
<div className="absolute bottom-[5%] right-[5%] opacity-5 select-none">
|
|
669
|
+
<h1 className="text-[15vw] font-bold leading-none text-[var(--color-text,#0a0a0a)] font-mono tracking-tighter text-right">
|
|
670
|
+
WALLET
|
|
671
|
+
</h1>
|
|
672
|
+
</div>
|
|
673
|
+
|
|
674
|
+
{/* Corner finder patterns */}
|
|
675
|
+
<div className="absolute top-10 left-10 w-32 h-32 border-l-4 border-t-4 border-[var(--color-text,#0a0a0a)] opacity-10">
|
|
676
|
+
<div className="absolute top-2 left-2 w-4 h-4 bg-[var(--color-text,#0a0a0a)]" />
|
|
677
|
+
</div>
|
|
678
|
+
<div className="absolute bottom-10 right-10 w-32 h-32 border-r-4 border-b-4 border-[var(--color-text,#0a0a0a)] opacity-10 flex items-end justify-end">
|
|
679
|
+
<div className="absolute bottom-2 right-2 w-4 h-4 bg-[var(--color-text,#0a0a0a)]" />
|
|
680
|
+
</div>
|
|
681
|
+
</div>
|
|
682
|
+
|
|
683
|
+
{/* Logo header */}
|
|
684
|
+
<div className="fixed top-6 left-6 z-50 flex items-center gap-3">
|
|
685
|
+
<div className="w-10 h-10">
|
|
686
|
+
<img src="/logo.webp" alt="AuraWallet" className="w-full h-full object-contain" />
|
|
687
|
+
</div>
|
|
688
|
+
<div className="font-black text-xl tracking-tighter text-[var(--color-text,#0a0a0a)]">AURAWALLET</div>
|
|
689
|
+
</div>
|
|
690
|
+
|
|
691
|
+
{/* Nav */}
|
|
692
|
+
<div className="fixed top-7 right-6 z-50 flex items-center gap-3 font-mono text-[10px] tracking-widest">
|
|
693
|
+
<Link href="/docs" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">DOCS</Link>
|
|
694
|
+
<Link href="/api" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">API</Link>
|
|
695
|
+
<Link href="/app" className="text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] transition-colors">APP</Link>
|
|
696
|
+
<DocsThemeToggle />
|
|
697
|
+
</div>
|
|
698
|
+
|
|
699
|
+
{/* Unlock card */}
|
|
700
|
+
<div className="relative z-10 w-full max-w-[380px]">
|
|
701
|
+
<div className="bg-[var(--color-surface,#f4f4f2)] border border-[var(--color-border,#d4d4d8)] shadow-lg overflow-hidden font-mono">
|
|
702
|
+
{/* Card header bar */}
|
|
703
|
+
<div className="px-5 py-3 border-b border-[var(--color-border,#d4d4d8)] bg-[var(--color-surface-alt,#fafafa)] flex items-center justify-between">
|
|
704
|
+
<span className="font-sans font-bold text-sm text-[var(--color-text,#0a0a0a)] uppercase tracking-tight">
|
|
705
|
+
{pageState === 'setup' ? 'Initialize' : 'Unlock'}
|
|
706
|
+
</span>
|
|
707
|
+
<span className="text-[9px] text-[var(--color-text-faint,#9ca3af)] font-bold tracking-widest">
|
|
708
|
+
{pageState === 'loading' ? 'CONNECTING...' : pageState === 'setup' ? 'NO_VAULT' : 'LOCKED'}
|
|
709
|
+
</span>
|
|
710
|
+
</div>
|
|
711
|
+
|
|
712
|
+
<div className="p-6">
|
|
713
|
+
{pageState === 'loading' && (
|
|
714
|
+
<div className="flex flex-col items-center py-12">
|
|
715
|
+
<div className="w-6 h-6 border-2 border-[var(--color-border,#d4d4d8)] border-t-[var(--color-text,#0a0a0a)] animate-spin" />
|
|
716
|
+
<div className="mt-4 text-[10px] text-[var(--color-text-muted,#6b7280)] tracking-widest">CONNECTING</div>
|
|
717
|
+
</div>
|
|
718
|
+
)}
|
|
719
|
+
|
|
720
|
+
{pageState === 'setup' && mnemonic && setupOnboardingStep === 'seed' && (
|
|
721
|
+
<div className="flex flex-col items-center">
|
|
722
|
+
<div className="w-16 h-16 mb-4">
|
|
723
|
+
<img src="/logo.webp" alt="AuraWallet" className="w-full h-full object-contain" />
|
|
724
|
+
</div>
|
|
725
|
+
<div className="text-[10px] text-[var(--color-text-muted,#6b7280)] tracking-widest text-center mb-4">
|
|
726
|
+
SAVE YOUR RECOVERY PHRASE
|
|
727
|
+
</div>
|
|
728
|
+
<div className="text-[9px] text-[var(--color-danger,#ef4444)] bg-[var(--color-danger,#ef4444)]/10 px-3 py-2 border border-[var(--color-danger,#ef4444)]/20 mb-3">
|
|
729
|
+
Write this down and store it securely. You will stay on this screen until you explicitly confirm.
|
|
730
|
+
</div>
|
|
731
|
+
{seedRecoveryNotice && (
|
|
732
|
+
<div className="text-[9px] text-[var(--color-info,#0047ff)] bg-[var(--color-info,#0047ff)]/10 px-3 py-2 border border-[var(--color-info,#0047ff)]/20 mb-3">
|
|
733
|
+
{seedRecoveryNotice}
|
|
734
|
+
</div>
|
|
735
|
+
)}
|
|
736
|
+
<div className="grid grid-cols-3 gap-2 w-full mb-4">
|
|
737
|
+
{mnemonic.split(' ').map((word, i) => (
|
|
738
|
+
<div key={i} className="text-[10px] font-mono text-[var(--color-text,#0a0a0a)] bg-[var(--color-background,#f4f4f5)] px-2 py-1 border border-[var(--color-border,#d4d4d8)]">
|
|
739
|
+
<span className="text-[var(--color-text-faint,#9ca3af)] mr-1">{i + 1}.</span>{word}
|
|
740
|
+
</div>
|
|
741
|
+
))}
|
|
742
|
+
</div>
|
|
743
|
+
<label className="flex items-start gap-2 w-full mb-3 cursor-pointer">
|
|
744
|
+
<input
|
|
745
|
+
type="checkbox"
|
|
746
|
+
checked={seedAcknowledged}
|
|
747
|
+
onChange={(e) => setSeedAcknowledged(e.target.checked)}
|
|
748
|
+
className="mt-0.5"
|
|
749
|
+
/>
|
|
750
|
+
<span className="text-[9px] text-[var(--color-text-muted,#6b7280)]">
|
|
751
|
+
I have written and verified this recovery phrase in a secure location.
|
|
752
|
+
</span>
|
|
753
|
+
</label>
|
|
754
|
+
<button
|
|
755
|
+
onClick={() => {
|
|
756
|
+
if (!seedAcknowledged) return;
|
|
757
|
+
setSetupOnboardingStep('trust');
|
|
758
|
+
}}
|
|
759
|
+
disabled={!seedAcknowledged}
|
|
760
|
+
className="w-full py-2.5 bg-[var(--color-text,#0a0a0a)] text-[var(--color-surface,#ffffff)] font-mono text-xs tracking-widest font-bold hover:opacity-90 transition-opacity disabled:opacity-30 disabled:cursor-not-allowed"
|
|
761
|
+
>
|
|
762
|
+
CONTINUE TO AGENT MODE
|
|
763
|
+
</button>
|
|
764
|
+
<div className="w-full mt-3 text-[8px] text-[var(--color-text-faint,#9ca3af)] text-center">
|
|
765
|
+
If you leave before confirming, this phrase is recoverable only temporarily in this tab session. If recovery expires, restart onboarding to regenerate.
|
|
766
|
+
</div>
|
|
767
|
+
</div>
|
|
768
|
+
)}
|
|
769
|
+
|
|
770
|
+
{pageState === 'setup' && mnemonic && setupOnboardingStep === 'trust' && (
|
|
771
|
+
<>
|
|
772
|
+
<div className="flex flex-col items-center mb-6">
|
|
773
|
+
<div className="w-16 h-16 mb-4">
|
|
774
|
+
<img src="/logo.webp" alt="AuraWallet" className="w-full h-full object-contain" />
|
|
775
|
+
</div>
|
|
776
|
+
<div className="text-[10px] text-[var(--color-text-muted,#6b7280)] tracking-widest text-center">
|
|
777
|
+
LOCAL AGENT MODE
|
|
778
|
+
</div>
|
|
779
|
+
</div>
|
|
780
|
+
|
|
781
|
+
<div className="space-y-4">
|
|
782
|
+
<div className="text-[9px] text-[var(--color-text-muted,#6b7280)] bg-[var(--color-background,#f4f4f5)] px-3 py-2 border border-[var(--color-border,#d4d4d8)]">
|
|
783
|
+
Choose how local Unix-socket agents are issued default permissions. You can change this later in settings.
|
|
784
|
+
</div>
|
|
785
|
+
|
|
786
|
+
<fieldset className="border border-[var(--color-border,#d4d4d8)] p-2.5 bg-[var(--color-background,#f4f4f5)]">
|
|
787
|
+
<legend className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest uppercase px-1">
|
|
788
|
+
Local Agent Mode
|
|
789
|
+
</legend>
|
|
790
|
+
<div className="space-y-2">
|
|
791
|
+
<label className="flex items-start gap-2 cursor-pointer">
|
|
792
|
+
<input
|
|
793
|
+
type="radio"
|
|
794
|
+
name="local-agent-mode"
|
|
795
|
+
checked={localAgentMode === 'dev'}
|
|
796
|
+
onChange={() => setLocalAgentMode('dev')}
|
|
797
|
+
className="mt-0.5"
|
|
798
|
+
/>
|
|
799
|
+
<span className="text-[9px] text-[var(--color-text-muted,#6b7280)] leading-relaxed">
|
|
800
|
+
Dev (recommended): auto-approve enabled with scoped non-financial profile.
|
|
801
|
+
</span>
|
|
802
|
+
</label>
|
|
803
|
+
<label className="flex items-start gap-2 cursor-pointer">
|
|
804
|
+
<input
|
|
805
|
+
type="radio"
|
|
806
|
+
name="local-agent-mode"
|
|
807
|
+
checked={localAgentMode === 'strict'}
|
|
808
|
+
onChange={() => setLocalAgentMode('strict')}
|
|
809
|
+
className="mt-0.5"
|
|
810
|
+
/>
|
|
811
|
+
<span className="text-[9px] text-[var(--color-text-muted,#6b7280)] leading-relaxed">
|
|
812
|
+
Strict: disable local auto-approve. Every local agent token request needs manual approval.
|
|
813
|
+
</span>
|
|
814
|
+
</label>
|
|
815
|
+
<label className="flex items-start gap-2 cursor-pointer">
|
|
816
|
+
<input
|
|
817
|
+
type="radio"
|
|
818
|
+
name="local-agent-mode"
|
|
819
|
+
checked={localAgentMode === 'admin'}
|
|
820
|
+
onChange={() => setLocalAgentMode('admin')}
|
|
821
|
+
className="mt-0.5"
|
|
822
|
+
/>
|
|
823
|
+
<span className="text-[9px] text-[var(--color-danger,#ef4444)] leading-relaxed">
|
|
824
|
+
Admin (dangerous): broad local agent access. Not recommended for primary vault workflows.
|
|
825
|
+
</span>
|
|
826
|
+
</label>
|
|
827
|
+
</div>
|
|
828
|
+
</fieldset>
|
|
829
|
+
|
|
830
|
+
{error && (
|
|
831
|
+
<div className="text-[9px] text-[var(--color-danger,#ef4444)] bg-[var(--color-danger,#ef4444)]/10 px-3 py-2 border border-[var(--color-danger,#ef4444)]/20">
|
|
832
|
+
{error}
|
|
833
|
+
</div>
|
|
834
|
+
)}
|
|
835
|
+
|
|
836
|
+
<button
|
|
837
|
+
onClick={() => { void handleFinalizeOnboarding(); }}
|
|
838
|
+
disabled={loading}
|
|
839
|
+
className="w-full py-2.5 bg-[var(--color-text,#0a0a0a)] text-[var(--color-surface,#ffffff)] font-mono text-xs tracking-widest font-bold hover:opacity-90 transition-opacity disabled:opacity-30 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
|
840
|
+
>
|
|
841
|
+
{loading ? (
|
|
842
|
+
<>
|
|
843
|
+
<div className="w-3 h-3 border border-[var(--color-surface,#ffffff)] border-t-transparent animate-spin" />
|
|
844
|
+
SAVING...
|
|
845
|
+
</>
|
|
846
|
+
) : (
|
|
847
|
+
'SAVE MODE AND CONTINUE'
|
|
848
|
+
)}
|
|
849
|
+
</button>
|
|
850
|
+
</div>
|
|
851
|
+
</>
|
|
852
|
+
)}
|
|
853
|
+
|
|
854
|
+
{pageState === 'setup' && !mnemonic && (
|
|
855
|
+
<>
|
|
856
|
+
{/* Logo centered */}
|
|
857
|
+
<div className="flex flex-col items-center mb-6">
|
|
858
|
+
<div className="w-16 h-16 mb-4">
|
|
859
|
+
<img src="/logo.webp" alt="AuraWallet" className="w-full h-full object-contain" />
|
|
860
|
+
</div>
|
|
861
|
+
<div className="text-[10px] text-[var(--color-text-muted,#6b7280)] tracking-widest text-center">
|
|
862
|
+
CREATE YOUR ENCRYPTED VAULT
|
|
863
|
+
</div>
|
|
864
|
+
</div>
|
|
865
|
+
|
|
866
|
+
<form onSubmit={handleSetup} className="space-y-4">
|
|
867
|
+
<div>
|
|
868
|
+
<label className="block text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest mb-1.5 uppercase">
|
|
869
|
+
Encryption Password
|
|
870
|
+
</label>
|
|
871
|
+
<input
|
|
872
|
+
type="password"
|
|
873
|
+
value={password}
|
|
874
|
+
onChange={(e) => { setPassword(e.target.value); setError(null); }}
|
|
875
|
+
placeholder="Minimum 8 characters"
|
|
876
|
+
className="w-full px-3 py-2.5 border border-[var(--color-border,#d4d4d8)] font-mono text-sm text-[var(--color-text,#0a0a0a)] focus:outline-none focus:border-[var(--color-text,#0a0a0a)] bg-[var(--color-surface,#ffffff)] placeholder-[var(--color-text-faint,#9ca3af)] transition-colors"
|
|
877
|
+
autoFocus
|
|
878
|
+
/>
|
|
879
|
+
</div>
|
|
880
|
+
|
|
881
|
+
{error && (
|
|
882
|
+
<div className="text-[9px] text-[var(--color-danger,#ef4444)] bg-[var(--color-danger,#ef4444)]/10 px-3 py-2 border border-[var(--color-danger,#ef4444)]/20">
|
|
883
|
+
{error}
|
|
884
|
+
</div>
|
|
885
|
+
)}
|
|
886
|
+
|
|
887
|
+
<button
|
|
888
|
+
type="submit"
|
|
889
|
+
disabled={loading || password.length < 8}
|
|
890
|
+
className="w-full py-2.5 bg-[var(--color-text,#0a0a0a)] text-[var(--color-surface,#ffffff)] font-mono text-xs tracking-widest font-bold hover:opacity-90 transition-opacity disabled:opacity-30 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
|
891
|
+
>
|
|
892
|
+
{loading ? (
|
|
893
|
+
<>
|
|
894
|
+
<div className="w-3 h-3 border border-[var(--color-surface,#ffffff)] border-t-transparent animate-spin" />
|
|
895
|
+
INITIALIZING...
|
|
896
|
+
</>
|
|
897
|
+
) : (
|
|
898
|
+
'INITIALIZE VAULT'
|
|
899
|
+
)}
|
|
900
|
+
</button>
|
|
901
|
+
</form>
|
|
902
|
+
|
|
903
|
+
<div className="mt-4 pt-4 border-t border-[var(--color-border,#d4d4d8)]">
|
|
904
|
+
<div className="flex items-start gap-2">
|
|
905
|
+
<div className="w-1 h-1 bg-[var(--color-text-muted,#6b7280)] mt-1.5 flex-shrink-0" />
|
|
906
|
+
<span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] leading-relaxed">
|
|
907
|
+
This password encrypts your seed phrase locally. It never leaves your machine.
|
|
908
|
+
</span>
|
|
909
|
+
</div>
|
|
910
|
+
</div>
|
|
911
|
+
</>
|
|
912
|
+
)}
|
|
913
|
+
|
|
914
|
+
{pageState === 'locked' && (
|
|
915
|
+
<>
|
|
916
|
+
{/* Logo centered */}
|
|
917
|
+
<div className="flex flex-col items-center mb-6">
|
|
918
|
+
<div className="w-16 h-16 mb-4">
|
|
919
|
+
<img src="/logo.webp" alt="AuraWallet" className="w-full h-full object-contain" />
|
|
920
|
+
</div>
|
|
921
|
+
<div className="text-[10px] text-[var(--color-text-muted,#6b7280)] tracking-widest text-center">
|
|
922
|
+
ENTER PASSWORD TO UNLOCK
|
|
923
|
+
</div>
|
|
924
|
+
</div>
|
|
925
|
+
|
|
926
|
+
<form onSubmit={handleUnlock} className="space-y-4">
|
|
927
|
+
<div>
|
|
928
|
+
<label className="block text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-widest mb-1.5 uppercase">
|
|
929
|
+
Password
|
|
930
|
+
</label>
|
|
931
|
+
<input
|
|
932
|
+
type="password"
|
|
933
|
+
value={password}
|
|
934
|
+
onChange={(e) => { setPassword(e.target.value); setError(null); }}
|
|
935
|
+
placeholder="Enter vault password"
|
|
936
|
+
className="w-full px-3 py-2.5 border border-[var(--color-border,#d4d4d8)] font-mono text-sm text-[var(--color-text,#0a0a0a)] focus:outline-none focus:border-[var(--color-text,#0a0a0a)] bg-[var(--color-surface,#ffffff)] placeholder-[var(--color-text-faint,#9ca3af)] transition-colors"
|
|
937
|
+
autoFocus
|
|
938
|
+
/>
|
|
939
|
+
</div>
|
|
940
|
+
|
|
941
|
+
{error && (
|
|
942
|
+
<div className="text-[9px] text-[var(--color-danger,#ef4444)] bg-[var(--color-danger,#ef4444)]/10 px-3 py-2 border border-[var(--color-danger,#ef4444)]/20">
|
|
943
|
+
{error}
|
|
944
|
+
</div>
|
|
945
|
+
)}
|
|
946
|
+
|
|
947
|
+
<button
|
|
948
|
+
type="submit"
|
|
949
|
+
disabled={loading || !password}
|
|
950
|
+
className="w-full py-2.5 bg-[var(--color-text,#0a0a0a)] text-[var(--color-surface,#ffffff)] font-mono text-xs tracking-widest font-bold hover:opacity-90 transition-opacity disabled:opacity-30 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
|
951
|
+
>
|
|
952
|
+
{loading ? (
|
|
953
|
+
<>
|
|
954
|
+
<div className="w-3 h-3 border border-[var(--color-surface,#ffffff)] border-t-transparent animate-spin" />
|
|
955
|
+
UNLOCKING...
|
|
956
|
+
</>
|
|
957
|
+
) : (
|
|
958
|
+
'UNLOCK'
|
|
959
|
+
)}
|
|
960
|
+
</button>
|
|
961
|
+
</form>
|
|
962
|
+
</>
|
|
963
|
+
)}
|
|
964
|
+
</div>
|
|
965
|
+
|
|
966
|
+
{/* Barcode + stripe */}
|
|
967
|
+
<div className="flex items-center gap-3 px-5 py-2 border-t border-[var(--color-border,#d4d4d8)]">
|
|
968
|
+
<div className="h-4 flex-1 bg-[repeating-linear-gradient(90deg,var(--color-text,#000),var(--color-text,#000)_1px,transparent_1px,transparent_3px)] opacity-30" />
|
|
969
|
+
<span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-wider">AURAWALLET</span>
|
|
970
|
+
</div>
|
|
971
|
+
<div className="h-2 w-full" style={{
|
|
972
|
+
backgroundImage: 'repeating-linear-gradient(45deg, var(--color-text, #000), var(--color-text, #000) 5px, transparent 5px, transparent 10px)',
|
|
973
|
+
opacity: 0.1,
|
|
974
|
+
}} />
|
|
975
|
+
</div>
|
|
976
|
+
|
|
977
|
+
{/* Specimen label below card */}
|
|
978
|
+
<div className="mt-4 text-center">
|
|
979
|
+
<span className="text-[8px] text-[var(--color-text-faint,#9ca3af)] tracking-[0.2em] font-mono">
|
|
980
|
+
SECURE LOCAL WALLETS FOR AI AGENTS
|
|
981
|
+
</span>
|
|
982
|
+
</div>
|
|
983
|
+
</div>
|
|
984
|
+
</div>
|
|
985
|
+
);
|
|
986
|
+
}
|