auramaxx 0.0.1
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 +77 -0
- package/apps/desktop-electron/main.js +428 -0
- package/bin/auramaxx.js +1063 -0
- package/docs/ADAPTERS.md +466 -0
- package/docs/AGENT_SETUP.md +159 -0
- package/docs/API.md +127 -0
- package/docs/APPS.md +199 -0
- package/docs/ARCHITECTURE.md +235 -0
- package/docs/AUTH.md +318 -0
- package/docs/BEST-PRACTICES.md +82 -0
- package/docs/CLI.md +141 -0
- package/docs/DESKTOP_ELECTRON.md +26 -0
- package/docs/DEVELOPING-APPS.md +453 -0
- package/docs/MCP.md +122 -0
- package/docs/PACKAGING_POLICY.md +19 -0
- package/docs/PERMISSION.md +137 -0
- package/docs/PROTOCOL.md +142 -0
- package/docs/README.md +50 -0
- package/docs/SKILLS.md +132 -0
- package/docs/TROUBLESHOOTING.md +376 -0
- package/docs/WORKSPACE.md +673 -0
- package/docs/agent-auth.md +14 -0
- package/docs/api/authentication.md +79 -0
- package/docs/api/secrets/api-keys.md +28 -0
- package/docs/api/secrets/credentials.md +80 -0
- package/docs/api/secrets/sharing.md +48 -0
- package/docs/api/system.md +41 -0
- package/docs/api/wallets/apps-strategies.md +66 -0
- package/docs/api/wallets/core.md +46 -0
- package/docs/api/wallets/data-portfolio.md +42 -0
- package/docs/aura-file.md +48 -0
- package/docs/core-concepts/FEATURES.md +114 -0
- package/docs/credentials.md +120 -0
- package/docs/external/HOW_TO_AURAMAXX/GETTING_SECRETS.md +33 -0
- package/docs/external/HOW_TO_AURAMAXX/README.md +45 -0
- package/docs/external/getting-started.md +10 -0
- package/docs/external/overview.md +19 -0
- package/docs/external/persona-paths.md +7 -0
- package/docs/external/share-secret.md +76 -0
- package/docs/external/why-aura.md +7 -0
- package/docs/security.md +227 -0
- package/docs/templates/RELEASE_NOTES_TEMPLATE.md +22 -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 +28 -0
- package/package.json +167 -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/20260222090000_update_admin_ttl_default/migration.sql +10 -0
- package/prisma/migrations/migration_lock.toml +3 -0
- package/prisma/schema.prisma +447 -0
- package/public/logo.webp +0 -0
- package/scripts/add-app.js +245 -0
- package/server/abi/SwapHelper.json +438 -0
- package/server/cli/approval.ts +447 -0
- package/server/cli/commands/actions.ts +474 -0
- package/server/cli/commands/api.ts +220 -0
- package/server/cli/commands/apikey.ts +277 -0
- package/server/cli/commands/app.ts +204 -0
- package/server/cli/commands/auth.ts +464 -0
- package/server/cli/commands/cron.ts +24 -0
- package/server/cli/commands/diary.ts +274 -0
- package/server/cli/commands/doctor.ts +1247 -0
- package/server/cli/commands/env.ts +476 -0
- package/server/cli/commands/experimental.ts +69 -0
- package/server/cli/commands/init.ts +798 -0
- package/server/cli/commands/lock.ts +157 -0
- package/server/cli/commands/mcp.ts +285 -0
- package/server/cli/commands/quickhack.ts +86 -0
- package/server/cli/commands/release-check.ts +231 -0
- package/server/cli/commands/restore.ts +314 -0
- package/server/cli/commands/service.ts +320 -0
- package/server/cli/commands/shell-hook.ts +512 -0
- package/server/cli/commands/skill.ts +216 -0
- package/server/cli/commands/start.ts +139 -0
- package/server/cli/commands/status.ts +59 -0
- package/server/cli/commands/stop.ts +36 -0
- package/server/cli/commands/token.ts +180 -0
- package/server/cli/commands/unlock.ts +50 -0
- package/server/cli/commands/vault.ts +1323 -0
- package/server/cli/commands/wallet.ts +209 -0
- package/server/cli/index.ts +280 -0
- package/server/cli/lib/approval-poll.ts +94 -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 +280 -0
- package/server/cli/lib/dotenv-migrate.ts +116 -0
- package/server/cli/lib/dotenv-parser.ts +146 -0
- package/server/cli/lib/escalation.ts +57 -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/lock-unlock-helper.ts +71 -0
- package/server/cli/lib/process.ts +162 -0
- package/server/cli/lib/prompt.ts +294 -0
- package/server/cli/lib/theme.ts +240 -0
- package/server/cli/socket.ts +579 -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 +420 -0
- package/server/lib/adapters/factory.ts +119 -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 +419 -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 +258 -0
- package/server/lib/app-installer.ts +505 -0
- package/server/lib/app-tokens.ts +247 -0
- package/server/lib/approval-link.ts +27 -0
- package/server/lib/auth.ts +314 -0
- package/server/lib/auto-execute.ts +160 -0
- package/server/lib/batch.ts +242 -0
- package/server/lib/cold.ts +1048 -0
- package/server/lib/config.ts +408 -0
- package/server/lib/credential-access-audit.ts +85 -0
- package/server/lib/credential-access-policy.ts +111 -0
- package/server/lib/credential-health.ts +343 -0
- package/server/lib/credential-import.ts +608 -0
- package/server/lib/credential-scope.ts +102 -0
- package/server/lib/credential-shares.ts +190 -0
- package/server/lib/credential-transport.ts +533 -0
- package/server/lib/credential-vault.ts +77 -0
- package/server/lib/credentials.ts +422 -0
- package/server/lib/crypto.ts +8 -0
- package/server/lib/db.ts +58 -0
- package/server/lib/defaults.ts +386 -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/diary.ts +34 -0
- package/server/lib/dont-ask-again-policy.ts +41 -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 +114 -0
- package/server/lib/error.ts +20 -0
- package/server/lib/events.ts +217 -0
- package/server/lib/feature-flags.ts +93 -0
- package/server/lib/hot.ts +357 -0
- package/server/lib/human-action-summary.ts +80 -0
- package/server/lib/key-fingerprint.ts +28 -0
- package/server/lib/logger.ts +340 -0
- package/server/lib/network.ts +137 -0
- package/server/lib/notifications.ts +230 -0
- package/server/lib/oauth2-refresh.ts +241 -0
- package/server/lib/oursecret.ts +71 -0
- package/server/lib/passkey-credential.ts +360 -0
- package/server/lib/passkey.ts +68 -0
- package/server/lib/permissions.ts +299 -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 +297 -0
- package/server/lib/resolve-action.ts +328 -0
- package/server/lib/resolve.ts +36 -0
- package/server/lib/secret-gist-share.ts +296 -0
- package/server/lib/sessions.ts +634 -0
- package/server/lib/socket-path.ts +56 -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 +159 -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 +237 -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 +84 -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/update-check.ts +35 -0
- package/server/lib/verified-summary.ts +414 -0
- package/server/lib/view-registry.ts +80 -0
- package/server/mcp/profile-policy.ts +30 -0
- package/server/mcp/server.ts +1589 -0
- package/server/mcp/tools.ts +276 -0
- package/server/middleware/auth.ts +119 -0
- package/server/middleware/requestLogger.ts +84 -0
- package/server/routes/actions.ts +539 -0
- package/server/routes/adapters.ts +711 -0
- package/server/routes/addressbook.ts +113 -0
- package/server/routes/ai.ts +34 -0
- package/server/routes/apikeys.ts +343 -0
- package/server/routes/apps.ts +601 -0
- package/server/routes/auth.ts +406 -0
- package/server/routes/backup.ts +404 -0
- package/server/routes/batch.ts +270 -0
- package/server/routes/bookmarks.ts +162 -0
- package/server/routes/credential-shares.ts +380 -0
- package/server/routes/credential-vaults.ts +159 -0
- package/server/routes/credentials.ts +1782 -0
- package/server/routes/dashboard.ts +97 -0
- package/server/routes/defaults.ts +124 -0
- package/server/routes/flags.ts +11 -0
- package/server/routes/fund.ts +225 -0
- package/server/routes/heartbeat.ts +375 -0
- package/server/routes/import.ts +364 -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 +366 -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 +352 -0
- package/server/routes/swap-solana.ts +176 -0
- package/server/routes/swap.ts +356 -0
- package/server/routes/token.ts +247 -0
- package/server/routes/unlock.ts +467 -0
- package/server/routes/views.ts +41 -0
- package/server/routes/wallet-assets.ts +361 -0
- package/server/routes/wallet-transactions.ts +515 -0
- package/server/routes/wallet.ts +709 -0
- package/server/types.ts +146 -0
- package/shared/credential-field-schema.ts +248 -0
- package/skills/auramaxx/HEARTBEAT.md +78 -0
- package/skills/auramaxx/SKILL.md +745 -0
- package/skills/auramaxx/docs/AGENT_SETUP.md +155 -0
- package/skills/auramaxx/docs/API.md +127 -0
- package/skills/auramaxx/docs/AUTH.md +318 -0
- package/skills/auramaxx/docs/CLI.md +130 -0
- package/skills/auramaxx/docs/MCP.md +122 -0
- package/skills/auramaxx/docs/TROUBLESHOOTING.md +357 -0
- package/skills/auramaxx/docs/WORKSPACE.md +673 -0
- package/skills/auramaxx/docs/security.md +227 -0
- package/skills/task-lifecycle/SKILL.md +378 -0
- package/src/app/api/[...doc]/page.tsx +36 -0
- package/src/app/api/agent-requests/route.ts +30 -0
- package/src/app/api/apps/install/route.ts +132 -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/docs/plain/route.ts +74 -0
- package/src/app/api/events/route.ts +92 -0
- package/src/app/api/page.tsx +290 -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 +40 -0
- package/src/app/api/workspace/config/route.ts +121 -0
- package/src/app/api/workspace/import/route.ts +127 -0
- package/src/app/api/workspace/route.ts +116 -0
- package/src/app/app-legacy-do-not-use/page.tsx +2245 -0
- package/src/app/apple-icon.png +0 -0
- package/src/app/approve/[actionId]/page.tsx +409 -0
- package/src/app/docs/DocsPageContent.tsx +269 -0
- package/src/app/docs/[...doc]/page.tsx +41 -0
- package/src/app/docs/page.tsx +38 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +819 -0
- package/src/app/health/page.tsx +5 -0
- package/src/app/hello/page.tsx +102 -0
- package/src/app/icon.png +0 -0
- package/src/app/layout.tsx +39 -0
- package/src/app/page.tsx +1964 -0
- package/src/app/privacy/page.tsx +63 -0
- package/src/app/providers.tsx +87 -0
- package/src/app/share/[token]/page.tsx +295 -0
- package/src/app/terms/page.tsx +80 -0
- package/src/components/ChainSelector.tsx +44 -0
- package/src/components/HumanActionBar.tsx +697 -0
- package/src/components/NotificationDrawer.tsx +387 -0
- package/src/components/PasskeyEnrollmentPrompt.tsx +235 -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 +88 -0
- package/src/components/design-system/ChainIndicator.tsx +65 -0
- package/src/components/design-system/ChainSelector.tsx +147 -0
- package/src/components/design-system/ConfirmationModal.tsx +107 -0
- package/src/components/design-system/ConfirmationPopover.tsx +81 -0
- package/src/components/design-system/DownloadButton.tsx +149 -0
- package/src/components/design-system/Drawer.tsx +133 -0
- package/src/components/design-system/FilterDropdown.tsx +183 -0
- package/src/components/design-system/ItemPicker.tsx +157 -0
- package/src/components/design-system/Modal.tsx +296 -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 +65 -0
- package/src/components/design-system/TyvekCollapsibleSection.tsx +55 -0
- package/src/components/design-system/index.ts +14 -0
- package/src/components/docs/ClientSideMarkdown.tsx +51 -0
- package/src/components/docs/DocsSearchBar.tsx +118 -0
- package/src/components/docs/DocsThemeToggle.tsx +38 -0
- package/src/components/docs/PersistentDocGroup.tsx +91 -0
- package/src/components/docs/ShareUrlButton.tsx +33 -0
- package/src/components/docs/SidebarScrollMemory.tsx +56 -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/CreateViewModal.tsx +88 -0
- package/src/components/layout/LeftRail.tsx +114 -0
- package/src/components/layout/TabBar.tsx +284 -0
- package/src/components/layout/WalletSidebar.tsx +1030 -0
- package/src/components/layout/index.ts +6 -0
- package/src/components/marketing/AuraMaxxSpecOverlay.tsx +653 -0
- package/src/components/marketing/DeviceMorphExperience.tsx +216 -0
- package/src/components/vault/ApiKeysConsole.tsx +1272 -0
- package/src/components/vault/AuditConsole.tsx +600 -0
- package/src/components/vault/CredentialDetail.tsx +625 -0
- package/src/components/vault/CredentialEmpty.tsx +55 -0
- package/src/components/vault/CredentialField.tsx +583 -0
- package/src/components/vault/CredentialForm.tsx +1484 -0
- package/src/components/vault/CredentialList.tsx +265 -0
- package/src/components/vault/CredentialRow.tsx +130 -0
- package/src/components/vault/CredentialShareModal.tsx +273 -0
- package/src/components/vault/CredentialVault.tsx +1662 -0
- package/src/components/vault/CredentialWalletWidget.tsx +103 -0
- package/src/components/vault/DocsConsole.tsx +113 -0
- package/src/components/vault/ImportCredentialsModal.tsx +578 -0
- package/src/components/vault/LargeTypeModal.tsx +88 -0
- package/src/components/vault/PasswordGenerator.tsx +232 -0
- package/src/components/vault/TOTPDisplay.tsx +108 -0
- package/src/components/vault/TotpSetupPanel.tsx +198 -0
- package/src/components/vault/VaultSidebar.tsx +881 -0
- package/src/components/vault/credentialFormName.ts +91 -0
- package/src/components/vault/hooks/useVaultKeyboardShortcuts.ts +69 -0
- package/src/components/vault/types.ts +56 -0
- package/src/context/AuthContext.tsx +365 -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 +4 -0
- package/src/hooks/useAgentActions.ts +552 -0
- package/src/hooks/useBalance.ts +103 -0
- package/src/hooks/useBalances.ts +129 -0
- package/src/hooks/useTheme.ts +156 -0
- package/src/instrumentation.ts +12 -0
- package/src/lib/api-docs.ts +154 -0
- package/src/lib/api.ts +474 -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/credential-field-schema.ts +11 -0
- package/src/lib/crypto.ts +112 -0
- package/src/lib/db.ts +21 -0
- package/src/lib/docs.ts +544 -0
- package/src/lib/events.ts +363 -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/totp-import.ts +57 -0
- package/src/lib/vault-crypto.ts +129 -0
- package/src/lib/view-registry.ts +57 -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 +170 -0
- package/tailwind.config.ts +99 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState, useRef, useCallback, useEffect, useMemo } from 'react';
|
|
4
|
+
import { ChevronDown, Copy, Eye, EyeOff, Maximize2, Loader2 } from 'lucide-react';
|
|
5
|
+
import { Popover } from '@/components/design-system';
|
|
6
|
+
import { decryptCredentialPayload } from '@/lib/vault-crypto';
|
|
7
|
+
import { api, Api } from '@/lib/api';
|
|
8
|
+
import { canonicalizeCredentialFieldKey, NOTE_CONTENT_KEY } from '@/lib/credential-field-schema';
|
|
9
|
+
import { marked } from 'marked';
|
|
10
|
+
|
|
11
|
+
type SensitiveInteractionMode = 'default' | 'hover-copy' | 'markdown-hover-copy';
|
|
12
|
+
|
|
13
|
+
interface CredentialFieldProps {
|
|
14
|
+
label: string;
|
|
15
|
+
value?: string;
|
|
16
|
+
copyValue?: string;
|
|
17
|
+
trailingValue?: React.ReactNode;
|
|
18
|
+
credentialId: string;
|
|
19
|
+
fieldKey: string;
|
|
20
|
+
isSensitive: boolean;
|
|
21
|
+
onShowLargeType: (value: string) => void;
|
|
22
|
+
sensitiveInteractionMode?: SensitiveInteractionMode;
|
|
23
|
+
actionsPosition?: 'left' | 'right';
|
|
24
|
+
showActionsInHoverCopyMode?: boolean;
|
|
25
|
+
sensitiveClickBehavior?: 'reveal' | 'copy';
|
|
26
|
+
disableLargeType?: boolean;
|
|
27
|
+
renderMarkdown?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const CredentialField: React.FC<CredentialFieldProps> = ({
|
|
31
|
+
label,
|
|
32
|
+
value,
|
|
33
|
+
copyValue,
|
|
34
|
+
trailingValue,
|
|
35
|
+
credentialId,
|
|
36
|
+
fieldKey,
|
|
37
|
+
isSensitive,
|
|
38
|
+
onShowLargeType,
|
|
39
|
+
sensitiveInteractionMode = 'default',
|
|
40
|
+
actionsPosition = 'left',
|
|
41
|
+
showActionsInHoverCopyMode = false,
|
|
42
|
+
sensitiveClickBehavior = 'reveal',
|
|
43
|
+
disableLargeType = false,
|
|
44
|
+
renderMarkdown = false,
|
|
45
|
+
}) => {
|
|
46
|
+
const [copied, setCopied] = useState(false);
|
|
47
|
+
const [revealed, setRevealed] = useState(false);
|
|
48
|
+
const [revealedValue, setRevealedValue] = useState<string | null>(null);
|
|
49
|
+
const [decrypting, setDecrypting] = useState(false);
|
|
50
|
+
const [error, setError] = useState<string | null>(null);
|
|
51
|
+
const [menuOpen, setMenuOpen] = useState(false);
|
|
52
|
+
const menuAnchorRef = useRef<HTMLButtonElement>(null);
|
|
53
|
+
const revealTimerRef = useRef<ReturnType<typeof setTimeout>>(null);
|
|
54
|
+
const copyTimerRef = useRef<ReturnType<typeof setTimeout>>(null);
|
|
55
|
+
const isHoverCopyMode = sensitiveInteractionMode === 'hover-copy' || sensitiveInteractionMode === 'markdown-hover-copy';
|
|
56
|
+
const isMarkdownMode = sensitiveInteractionMode === 'markdown-hover-copy';
|
|
57
|
+
const isRowHoverCopyMode = sensitiveInteractionMode === 'hover-copy';
|
|
58
|
+
const isSensitiveClickCopyMode = !isHoverCopyMode && sensitiveClickBehavior === 'copy';
|
|
59
|
+
const canShowSensitiveActions = !isHoverCopyMode || showActionsInHoverCopyMode;
|
|
60
|
+
|
|
61
|
+
const markdownHtml = useMemo(() => {
|
|
62
|
+
if (!isMarkdownMode || revealedValue == null) return '';
|
|
63
|
+
const escaped = revealedValue
|
|
64
|
+
.replace(/&/g, '&')
|
|
65
|
+
.replace(/</g, '<')
|
|
66
|
+
.replace(/>/g, '>');
|
|
67
|
+
return marked.parse(escaped, { async: false, breaks: true }) as string;
|
|
68
|
+
}, [isMarkdownMode, revealedValue]);
|
|
69
|
+
|
|
70
|
+
const plainMarkdownHtml = useMemo(() => {
|
|
71
|
+
if (!renderMarkdown || !value) return '';
|
|
72
|
+
const escaped = value
|
|
73
|
+
.replace(/&/g, '&')
|
|
74
|
+
.replace(/</g, '<')
|
|
75
|
+
.replace(/>/g, '>');
|
|
76
|
+
return marked.parse(escaped, { async: false, breaks: true }) as string;
|
|
77
|
+
}, [renderMarkdown, value]);
|
|
78
|
+
|
|
79
|
+
useEffect(() => {
|
|
80
|
+
return () => {
|
|
81
|
+
if (revealTimerRef.current) clearTimeout(revealTimerRef.current);
|
|
82
|
+
if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
|
|
83
|
+
};
|
|
84
|
+
}, []);
|
|
85
|
+
|
|
86
|
+
useEffect(() => {
|
|
87
|
+
setCopied(false);
|
|
88
|
+
setRevealed(false);
|
|
89
|
+
setRevealedValue(null);
|
|
90
|
+
setError(null);
|
|
91
|
+
setMenuOpen(false);
|
|
92
|
+
if (revealTimerRef.current) clearTimeout(revealTimerRef.current);
|
|
93
|
+
}, [credentialId, fieldKey]);
|
|
94
|
+
|
|
95
|
+
// Auto-decrypt intentionally removed for markdown notes to avoid layout shift.
|
|
96
|
+
// User clicks to reveal first, then clicks again to copy.
|
|
97
|
+
|
|
98
|
+
const markCopied = useCallback(() => {
|
|
99
|
+
setCopied(true);
|
|
100
|
+
if (copyTimerRef.current) clearTimeout(copyTimerRef.current);
|
|
101
|
+
copyTimerRef.current = setTimeout(() => setCopied(false), 2000);
|
|
102
|
+
}, []);
|
|
103
|
+
|
|
104
|
+
const revealForWindow = useCallback((val: string) => {
|
|
105
|
+
setRevealedValue(val);
|
|
106
|
+
setRevealed(true);
|
|
107
|
+
if (revealTimerRef.current) clearTimeout(revealTimerRef.current);
|
|
108
|
+
revealTimerRef.current = setTimeout(() => {
|
|
109
|
+
setRevealed(false);
|
|
110
|
+
setRevealedValue(null);
|
|
111
|
+
}, 30000);
|
|
112
|
+
}, []);
|
|
113
|
+
|
|
114
|
+
// Silent decrypt — no spinner / no visual state changes.
|
|
115
|
+
// Use for copy and large-type where layout must stay stable.
|
|
116
|
+
const decryptFieldSilent = useCallback(async (): Promise<string | null> => {
|
|
117
|
+
if (revealedValue != null) return revealedValue;
|
|
118
|
+
try {
|
|
119
|
+
const res = await api.post<{ encrypted: string }>(Api.Wallet, `/credentials/${credentialId}/read`);
|
|
120
|
+
const plaintext = await decryptCredentialPayload(res.encrypted);
|
|
121
|
+
const parsed = JSON.parse(plaintext) as {
|
|
122
|
+
type?: string;
|
|
123
|
+
fields?: Array<{ key: string; value: string }>;
|
|
124
|
+
};
|
|
125
|
+
const credentialType = typeof parsed.type === 'string' ? parsed.type : '';
|
|
126
|
+
const canonicalTargetKey = canonicalizeCredentialFieldKey(credentialType, fieldKey);
|
|
127
|
+
const field = parsed.fields?.find((f) => (
|
|
128
|
+
canonicalizeCredentialFieldKey(credentialType, f.key) === canonicalTargetKey
|
|
129
|
+
|| (fieldKey === NOTE_CONTENT_KEY && f.key === 'value')
|
|
130
|
+
));
|
|
131
|
+
if (!field) return null;
|
|
132
|
+
return field.value;
|
|
133
|
+
} catch {
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
}, [credentialId, fieldKey, revealedValue]);
|
|
137
|
+
|
|
138
|
+
// Visual decrypt — shows spinner. Used for reveal where user expects feedback.
|
|
139
|
+
const decryptField = useCallback(async (): Promise<string | null> => {
|
|
140
|
+
if (revealedValue != null) return revealedValue;
|
|
141
|
+
setDecrypting(true);
|
|
142
|
+
setError(null);
|
|
143
|
+
try {
|
|
144
|
+
const val = await decryptFieldSilent();
|
|
145
|
+
if (val == null) {
|
|
146
|
+
setError('Field not found');
|
|
147
|
+
}
|
|
148
|
+
return val;
|
|
149
|
+
} catch {
|
|
150
|
+
setError('Decryption failed -- try re-unlocking');
|
|
151
|
+
return null;
|
|
152
|
+
} finally {
|
|
153
|
+
setDecrypting(false);
|
|
154
|
+
}
|
|
155
|
+
}, [decryptFieldSilent, revealedValue]);
|
|
156
|
+
|
|
157
|
+
const handleCopySensitive = useCallback(async () => {
|
|
158
|
+
if (decrypting) return;
|
|
159
|
+
setMenuOpen(false);
|
|
160
|
+
const val = await decryptFieldSilent();
|
|
161
|
+
if (val != null) {
|
|
162
|
+
navigator.clipboard.writeText(val);
|
|
163
|
+
markCopied();
|
|
164
|
+
}
|
|
165
|
+
}, [decryptFieldSilent, decrypting, markCopied]);
|
|
166
|
+
|
|
167
|
+
const handleReveal = useCallback(async () => {
|
|
168
|
+
if (decrypting || (revealed && revealedValue != null)) return;
|
|
169
|
+
setMenuOpen(false);
|
|
170
|
+
const val = await decryptField();
|
|
171
|
+
if (val != null) {
|
|
172
|
+
revealForWindow(val);
|
|
173
|
+
}
|
|
174
|
+
}, [decryptField, decrypting, revealForWindow, revealed, revealedValue]);
|
|
175
|
+
|
|
176
|
+
const handleHide = useCallback(() => {
|
|
177
|
+
setRevealed(false);
|
|
178
|
+
setRevealedValue(null);
|
|
179
|
+
if (revealTimerRef.current) clearTimeout(revealTimerRef.current);
|
|
180
|
+
}, []);
|
|
181
|
+
|
|
182
|
+
const handleLargeType = useCallback(async () => {
|
|
183
|
+
setMenuOpen(false);
|
|
184
|
+
const val = await decryptFieldSilent();
|
|
185
|
+
if (val != null) {
|
|
186
|
+
onShowLargeType(val);
|
|
187
|
+
}
|
|
188
|
+
}, [decryptFieldSilent, onShowLargeType]);
|
|
189
|
+
|
|
190
|
+
const handleCopyNonSensitive = useCallback(() => {
|
|
191
|
+
const valueToCopy = copyValue ?? value;
|
|
192
|
+
if (valueToCopy) {
|
|
193
|
+
navigator.clipboard.writeText(valueToCopy);
|
|
194
|
+
markCopied();
|
|
195
|
+
}
|
|
196
|
+
}, [copyValue, value, markCopied]);
|
|
197
|
+
|
|
198
|
+
const handleLargeTypeNonSensitive = useCallback(() => {
|
|
199
|
+
if (!value) return;
|
|
200
|
+
setMenuOpen(false);
|
|
201
|
+
onShowLargeType(value);
|
|
202
|
+
}, [onShowLargeType, value]);
|
|
203
|
+
|
|
204
|
+
const handleClickCopySensitive = useCallback(async () => {
|
|
205
|
+
if (!isHoverCopyMode || decrypting) return;
|
|
206
|
+
let val = revealedValue;
|
|
207
|
+
if (val == null) {
|
|
208
|
+
val = await decryptField();
|
|
209
|
+
if (val != null) setRevealedValue(val);
|
|
210
|
+
}
|
|
211
|
+
if (val != null) {
|
|
212
|
+
if (isRowHoverCopyMode) revealForWindow(val);
|
|
213
|
+
navigator.clipboard.writeText(val);
|
|
214
|
+
markCopied();
|
|
215
|
+
}
|
|
216
|
+
}, [decryptField, decrypting, isHoverCopyMode, isRowHoverCopyMode, markCopied, revealForWindow, revealedValue]);
|
|
217
|
+
|
|
218
|
+
const handleSensitiveContentClick = useCallback((event: React.MouseEvent<HTMLDivElement>) => {
|
|
219
|
+
if (isRowHoverCopyMode || isSensitiveClickCopyMode) return;
|
|
220
|
+
event.stopPropagation();
|
|
221
|
+
if (isHoverCopyMode) {
|
|
222
|
+
void handleClickCopySensitive();
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
void handleReveal();
|
|
226
|
+
}, [
|
|
227
|
+
handleClickCopySensitive,
|
|
228
|
+
handleReveal,
|
|
229
|
+
isHoverCopyMode,
|
|
230
|
+
isRowHoverCopyMode,
|
|
231
|
+
isSensitiveClickCopyMode,
|
|
232
|
+
]);
|
|
233
|
+
|
|
234
|
+
if (!isSensitive) {
|
|
235
|
+
const actions = (
|
|
236
|
+
<div className="shrink-0 relative flex items-center gap-1">
|
|
237
|
+
<button
|
|
238
|
+
type="button"
|
|
239
|
+
onClick={(e) => {
|
|
240
|
+
e.stopPropagation();
|
|
241
|
+
handleCopyNonSensitive();
|
|
242
|
+
}}
|
|
243
|
+
className="inline-flex w-[56px] justify-start items-center gap-1 px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity font-mono text-[8px] uppercase tracking-widest text-[var(--color-text-faint,#9ca3af)] hover:text-[var(--color-text,#0a0a0a)]"
|
|
244
|
+
>
|
|
245
|
+
<Copy size={10} />
|
|
246
|
+
{copied ? 'Copied' : 'Copy'}
|
|
247
|
+
</button>
|
|
248
|
+
<button
|
|
249
|
+
ref={menuAnchorRef}
|
|
250
|
+
onClick={(e) => {
|
|
251
|
+
e.stopPropagation();
|
|
252
|
+
setMenuOpen(!menuOpen);
|
|
253
|
+
}}
|
|
254
|
+
className="p-1 opacity-0 group-hover:opacity-100 transition-opacity text-[var(--color-text-faint,#9ca3af)] hover:text-[var(--color-text,#0a0a0a)]"
|
|
255
|
+
aria-label={`Field actions for ${label}`}
|
|
256
|
+
>
|
|
257
|
+
<ChevronDown size={12} />
|
|
258
|
+
</button>
|
|
259
|
+
<Popover
|
|
260
|
+
isOpen={menuOpen}
|
|
261
|
+
onClose={() => setMenuOpen(false)}
|
|
262
|
+
anchorEl={menuAnchorRef.current}
|
|
263
|
+
anchor="right"
|
|
264
|
+
>
|
|
265
|
+
<div className="flex flex-col gap-1 min-w-[160px]">
|
|
266
|
+
<button
|
|
267
|
+
onClick={handleCopyNonSensitive}
|
|
268
|
+
className="flex items-center gap-2 px-2 py-1.5 text-left font-mono text-[9px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-background-alt,#f4f4f5)] transition-colors"
|
|
269
|
+
>
|
|
270
|
+
<Copy size={10} />
|
|
271
|
+
Copy
|
|
272
|
+
</button>
|
|
273
|
+
<button
|
|
274
|
+
onClick={handleLargeTypeNonSensitive}
|
|
275
|
+
disabled={!value || disableLargeType}
|
|
276
|
+
className="flex items-center gap-2 px-2 py-1.5 text-left font-mono text-[9px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-background-alt,#f4f4f5)] transition-colors disabled:opacity-40"
|
|
277
|
+
>
|
|
278
|
+
<Maximize2 size={10} />
|
|
279
|
+
Show in large type
|
|
280
|
+
</button>
|
|
281
|
+
</div>
|
|
282
|
+
</Popover>
|
|
283
|
+
</div>
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
if (renderMarkdown && value) {
|
|
287
|
+
return (
|
|
288
|
+
<div
|
|
289
|
+
className="flex flex-wrap lg:flex-nowrap gap-x-3 gap-y-1 py-2 px-2 cursor-pointer hover:bg-[var(--color-background-alt,#f4f4f5)] transition-colors group items-start"
|
|
290
|
+
onClick={handleCopyNonSensitive}
|
|
291
|
+
>
|
|
292
|
+
{actionsPosition === 'left' && actions}
|
|
293
|
+
<div className="w-24 shrink-0">
|
|
294
|
+
<span className="font-mono text-[9px] uppercase tracking-widest text-[var(--color-text-muted,#6b7280)]">
|
|
295
|
+
{copied ? 'Copied' : label}
|
|
296
|
+
</span>
|
|
297
|
+
</div>
|
|
298
|
+
<div className="basis-full lg:basis-auto lg:flex-1 min-w-0 prose-mono max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0" dangerouslySetInnerHTML={{ __html: plainMarkdownHtml }} />
|
|
299
|
+
{actionsPosition === 'right' && actions}
|
|
300
|
+
</div>
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return (
|
|
305
|
+
<div
|
|
306
|
+
className="flex flex-wrap lg:flex-nowrap items-center gap-x-3 gap-y-1 py-2 px-2 cursor-pointer hover:bg-[var(--color-background-alt,#f4f4f5)] transition-colors group"
|
|
307
|
+
onClick={handleCopyNonSensitive}
|
|
308
|
+
>
|
|
309
|
+
{actionsPosition === 'left' && actions}
|
|
310
|
+
<div className="w-24 shrink-0">
|
|
311
|
+
<span className="font-mono text-[9px] uppercase tracking-widest text-[var(--color-text-muted,#6b7280)]">
|
|
312
|
+
{label}
|
|
313
|
+
</span>
|
|
314
|
+
</div>
|
|
315
|
+
<div className="basis-full lg:basis-auto lg:flex-1 min-w-0 min-h-[24px] flex items-center">
|
|
316
|
+
<span className="block min-w-0 font-mono text-[11px] text-[var(--color-text,#0a0a0a)] truncate whitespace-nowrap">
|
|
317
|
+
{copied ? 'Copied' : (value || '--')}
|
|
318
|
+
</span>
|
|
319
|
+
{trailingValue}
|
|
320
|
+
</div>
|
|
321
|
+
{actionsPosition === 'right' && actions}
|
|
322
|
+
</div>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const sensitiveActions = canShowSensitiveActions ? (
|
|
327
|
+
<div className="shrink-0 relative flex items-center gap-1">
|
|
328
|
+
<button
|
|
329
|
+
type="button"
|
|
330
|
+
onClick={(e) => {
|
|
331
|
+
e.stopPropagation();
|
|
332
|
+
void handleCopySensitive();
|
|
333
|
+
}}
|
|
334
|
+
className="inline-flex w-[56px] justify-start items-center gap-1 px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity font-mono text-[8px] uppercase tracking-widest text-[var(--color-text-faint,#9ca3af)] hover:text-[var(--color-text,#0a0a0a)]"
|
|
335
|
+
>
|
|
336
|
+
<Copy size={10} />
|
|
337
|
+
{copied ? 'Copied' : 'Copy'}
|
|
338
|
+
</button>
|
|
339
|
+
<button
|
|
340
|
+
ref={menuAnchorRef}
|
|
341
|
+
onClick={(e) => {
|
|
342
|
+
e.stopPropagation();
|
|
343
|
+
setMenuOpen(!menuOpen);
|
|
344
|
+
}}
|
|
345
|
+
className="p-1 opacity-0 group-hover:opacity-100 transition-opacity text-[var(--color-text-faint,#9ca3af)] hover:text-[var(--color-text,#0a0a0a)]"
|
|
346
|
+
aria-label={`Field actions for ${label}`}
|
|
347
|
+
>
|
|
348
|
+
<ChevronDown size={12} />
|
|
349
|
+
</button>
|
|
350
|
+
<Popover
|
|
351
|
+
isOpen={menuOpen}
|
|
352
|
+
onClose={() => setMenuOpen(false)}
|
|
353
|
+
anchorEl={menuAnchorRef.current}
|
|
354
|
+
anchor="right"
|
|
355
|
+
>
|
|
356
|
+
<div className="flex flex-col gap-1 min-w-[170px]">
|
|
357
|
+
<button
|
|
358
|
+
onClick={handleCopySensitive}
|
|
359
|
+
className="flex items-center gap-2 px-2 py-1.5 text-left font-mono text-[9px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-background-alt,#f4f4f5)] transition-colors"
|
|
360
|
+
>
|
|
361
|
+
<Copy size={10} />
|
|
362
|
+
Copy
|
|
363
|
+
</button>
|
|
364
|
+
{!isHoverCopyMode && !revealed && (
|
|
365
|
+
<button
|
|
366
|
+
onClick={handleReveal}
|
|
367
|
+
className="flex items-center gap-2 px-2 py-1.5 text-left font-mono text-[9px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-background-alt,#f4f4f5)] transition-colors"
|
|
368
|
+
>
|
|
369
|
+
<Eye size={10} />
|
|
370
|
+
Reveal
|
|
371
|
+
</button>
|
|
372
|
+
)}
|
|
373
|
+
{!isHoverCopyMode && revealed && (
|
|
374
|
+
<button
|
|
375
|
+
onClick={handleHide}
|
|
376
|
+
className="flex items-center gap-2 px-2 py-1.5 text-left font-mono text-[9px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-background-alt,#f4f4f5)] transition-colors"
|
|
377
|
+
>
|
|
378
|
+
<EyeOff size={10} />
|
|
379
|
+
Hide
|
|
380
|
+
</button>
|
|
381
|
+
)}
|
|
382
|
+
{!disableLargeType && (
|
|
383
|
+
<button
|
|
384
|
+
onClick={handleLargeType}
|
|
385
|
+
className="flex items-center gap-2 px-2 py-1.5 text-left font-mono text-[9px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-background-alt,#f4f4f5)] transition-colors"
|
|
386
|
+
>
|
|
387
|
+
<Maximize2 size={10} />
|
|
388
|
+
Show in large type
|
|
389
|
+
</button>
|
|
390
|
+
)}
|
|
391
|
+
</div>
|
|
392
|
+
</Popover>
|
|
393
|
+
</div>
|
|
394
|
+
) : null;
|
|
395
|
+
|
|
396
|
+
if (isMarkdownMode) {
|
|
397
|
+
const handleMarkdownClick = () => {
|
|
398
|
+
if (revealed && revealedValue != null) {
|
|
399
|
+
void handleCopySensitive();
|
|
400
|
+
} else {
|
|
401
|
+
void handleReveal();
|
|
402
|
+
}
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const markdownActions = (
|
|
406
|
+
<div className="shrink-0 relative flex items-center gap-1">
|
|
407
|
+
<button
|
|
408
|
+
type="button"
|
|
409
|
+
onClick={(e) => {
|
|
410
|
+
e.stopPropagation();
|
|
411
|
+
void handleCopySensitive();
|
|
412
|
+
}}
|
|
413
|
+
className="inline-flex w-[56px] justify-start items-center gap-1 px-2 py-1 opacity-0 group-hover:opacity-100 transition-opacity font-mono text-[8px] uppercase tracking-widest text-[var(--color-text-faint,#9ca3af)] hover:text-[var(--color-text,#0a0a0a)]"
|
|
414
|
+
>
|
|
415
|
+
<Copy size={10} />
|
|
416
|
+
{copied ? 'Copied' : 'Copy'}
|
|
417
|
+
</button>
|
|
418
|
+
<button
|
|
419
|
+
ref={menuAnchorRef}
|
|
420
|
+
onClick={(e) => {
|
|
421
|
+
e.stopPropagation();
|
|
422
|
+
setMenuOpen(!menuOpen);
|
|
423
|
+
}}
|
|
424
|
+
className="p-1 opacity-0 group-hover:opacity-100 transition-opacity text-[var(--color-text-faint,#9ca3af)] hover:text-[var(--color-text,#0a0a0a)]"
|
|
425
|
+
aria-label={`Field actions for ${label}`}
|
|
426
|
+
>
|
|
427
|
+
<ChevronDown size={12} />
|
|
428
|
+
</button>
|
|
429
|
+
<Popover
|
|
430
|
+
isOpen={menuOpen}
|
|
431
|
+
onClose={() => setMenuOpen(false)}
|
|
432
|
+
anchorEl={menuAnchorRef.current}
|
|
433
|
+
anchor="right"
|
|
434
|
+
>
|
|
435
|
+
<div className="flex flex-col gap-1 min-w-[170px]">
|
|
436
|
+
<button
|
|
437
|
+
onClick={handleCopySensitive}
|
|
438
|
+
className="flex items-center gap-2 px-2 py-1.5 text-left font-mono text-[9px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-background-alt,#f4f4f5)] transition-colors"
|
|
439
|
+
>
|
|
440
|
+
<Copy size={10} />
|
|
441
|
+
Copy
|
|
442
|
+
</button>
|
|
443
|
+
{!revealed ? (
|
|
444
|
+
<button
|
|
445
|
+
onClick={handleReveal}
|
|
446
|
+
className="flex items-center gap-2 px-2 py-1.5 text-left font-mono text-[9px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-background-alt,#f4f4f5)] transition-colors"
|
|
447
|
+
>
|
|
448
|
+
<Eye size={10} />
|
|
449
|
+
Reveal
|
|
450
|
+
</button>
|
|
451
|
+
) : (
|
|
452
|
+
<button
|
|
453
|
+
onClick={handleHide}
|
|
454
|
+
className="flex items-center gap-2 px-2 py-1.5 text-left font-mono text-[9px] tracking-widest uppercase text-[var(--color-text-muted,#6b7280)] hover:text-[var(--color-text,#0a0a0a)] hover:bg-[var(--color-background-alt,#f4f4f5)] transition-colors"
|
|
455
|
+
>
|
|
456
|
+
<EyeOff size={10} />
|
|
457
|
+
Hide
|
|
458
|
+
</button>
|
|
459
|
+
)}
|
|
460
|
+
</div>
|
|
461
|
+
</Popover>
|
|
462
|
+
</div>
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
return (
|
|
466
|
+
<div
|
|
467
|
+
data-testid={`credential-field-row-${fieldKey}`}
|
|
468
|
+
className="flex flex-wrap lg:flex-nowrap gap-x-3 gap-y-1 py-2 px-2 group transition-colors cursor-pointer hover:bg-[var(--color-background-alt,#f4f4f5)] items-start"
|
|
469
|
+
onClick={handleMarkdownClick}
|
|
470
|
+
>
|
|
471
|
+
{actionsPosition === 'left' && markdownActions}
|
|
472
|
+
<div className="w-24 shrink-0">
|
|
473
|
+
<span className="font-mono text-[9px] uppercase tracking-widest text-[var(--color-text-muted,#6b7280)]">
|
|
474
|
+
{copied ? 'Copied' : label}
|
|
475
|
+
</span>
|
|
476
|
+
</div>
|
|
477
|
+
<div
|
|
478
|
+
data-testid={`credential-field-value-${fieldKey}`}
|
|
479
|
+
className="basis-full lg:basis-auto lg:flex-1 min-w-0"
|
|
480
|
+
>
|
|
481
|
+
{error ? (
|
|
482
|
+
<span className="font-mono text-[10px] text-[var(--color-danger,#ef4444)]">{error}</span>
|
|
483
|
+
) : decrypting ? (
|
|
484
|
+
<Loader2 size={12} className="animate-spin text-[var(--color-text-muted,#6b7280)]" />
|
|
485
|
+
) : revealed && revealedValue != null ? (
|
|
486
|
+
<div className="prose-mono max-w-none [&>*:first-child]:mt-0 [&>*:last-child]:mb-0" dangerouslySetInnerHTML={{ __html: markdownHtml }} />
|
|
487
|
+
) : (
|
|
488
|
+
<span className="font-mono text-[11px] text-[var(--color-text-muted,#6b7280)] tracking-wider">
|
|
489
|
+
{'\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022'}
|
|
490
|
+
</span>
|
|
491
|
+
)}
|
|
492
|
+
</div>
|
|
493
|
+
{actionsPosition === 'right' && markdownActions}
|
|
494
|
+
</div>
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
return (
|
|
499
|
+
<div
|
|
500
|
+
data-testid={`credential-field-row-${fieldKey}`}
|
|
501
|
+
className={`flex flex-wrap lg:flex-nowrap gap-x-3 gap-y-1 py-2 px-2 group transition-colors rounded-sm items-center ${(isRowHoverCopyMode || isSensitiveClickCopyMode) ? 'cursor-pointer hover:bg-[var(--color-background-alt,#f4f4f5)]' : ''}`}
|
|
502
|
+
onClick={() => {
|
|
503
|
+
if (isRowHoverCopyMode) {
|
|
504
|
+
void handleClickCopySensitive();
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (isSensitiveClickCopyMode) void handleCopySensitive();
|
|
508
|
+
}}
|
|
509
|
+
>
|
|
510
|
+
{actionsPosition === 'left' && sensitiveActions}
|
|
511
|
+
<div className="w-24 shrink-0">
|
|
512
|
+
<span className="font-mono text-[9px] uppercase tracking-widest text-[var(--color-text-muted,#6b7280)]">
|
|
513
|
+
{label}
|
|
514
|
+
</span>
|
|
515
|
+
</div>
|
|
516
|
+
<div
|
|
517
|
+
data-testid={`credential-field-value-${fieldKey}`}
|
|
518
|
+
className={`basis-full lg:basis-auto lg:flex-1 min-w-0 transition-colors rounded-sm ${isHoverCopyMode ? '' : 'cursor-pointer'} ${`${(isRowHoverCopyMode || isSensitiveClickCopyMode) ? 'min-h-[24px] flex items-center' : 'hover:bg-[var(--color-background-alt,#f4f4f5)] min-h-[24px] flex items-center'} px-1 py-0.5`}`}
|
|
519
|
+
onClick={handleSensitiveContentClick}
|
|
520
|
+
>
|
|
521
|
+
{error ? (
|
|
522
|
+
<span className="font-mono text-[10px] text-[var(--color-danger,#ef4444)]">{error}</span>
|
|
523
|
+
) : decrypting ? (
|
|
524
|
+
<Loader2 size={12} className="animate-spin text-[var(--color-text-muted,#6b7280)]" />
|
|
525
|
+
) : isRowHoverCopyMode ? (
|
|
526
|
+
revealed && revealedValue != null ? (
|
|
527
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
528
|
+
<span className="block min-w-0 font-mono text-[11px] text-[var(--color-text,#0a0a0a)] truncate whitespace-nowrap">{revealedValue}</span>
|
|
529
|
+
<button
|
|
530
|
+
onClick={(e) => {
|
|
531
|
+
e.stopPropagation();
|
|
532
|
+
handleHide();
|
|
533
|
+
}}
|
|
534
|
+
className="shrink-0 text-[var(--color-text-faint,#9ca3af)] hover:text-[var(--color-text,#0a0a0a)] transition-colors"
|
|
535
|
+
>
|
|
536
|
+
<EyeOff size={12} />
|
|
537
|
+
</button>
|
|
538
|
+
</div>
|
|
539
|
+
) : (
|
|
540
|
+
<div className="w-full">
|
|
541
|
+
<div
|
|
542
|
+
data-testid={`credential-field-barcode-${fieldKey}`}
|
|
543
|
+
className="h-4 w-full bg-[repeating-linear-gradient(90deg,var(--color-text,#000),var(--color-text,#000)_1px,transparent_1px,transparent_3px)] opacity-30"
|
|
544
|
+
/>
|
|
545
|
+
<div
|
|
546
|
+
className="mt-1 h-1.5 w-full"
|
|
547
|
+
style={{
|
|
548
|
+
backgroundImage: 'repeating-linear-gradient(45deg, var(--color-text, #000), var(--color-text, #000) 5px, transparent 5px, transparent 10px)',
|
|
549
|
+
opacity: 0.1,
|
|
550
|
+
}}
|
|
551
|
+
/>
|
|
552
|
+
<div className="mt-1 font-mono text-[9px] text-[var(--color-text-muted,#6b7280)] tracking-wider uppercase">
|
|
553
|
+
{copied ? 'Copied' : 'Click to reveal and copy'}
|
|
554
|
+
</div>
|
|
555
|
+
</div>
|
|
556
|
+
)
|
|
557
|
+
) : isHoverCopyMode ? (
|
|
558
|
+
<span className="font-mono text-[10px] text-[var(--color-text-muted,#6b7280)] tracking-wider uppercase">
|
|
559
|
+
{copied ? 'Copied' : revealedValue != null ? 'Hover then click to copy' : 'Decrypting'}
|
|
560
|
+
</span>
|
|
561
|
+
) : revealed && revealedValue != null ? (
|
|
562
|
+
<div className="flex items-center gap-2 min-w-0">
|
|
563
|
+
<span className="block min-w-0 font-mono text-[11px] text-[var(--color-text,#0a0a0a)] truncate whitespace-nowrap">{revealedValue}</span>
|
|
564
|
+
<button
|
|
565
|
+
onClick={(e) => {
|
|
566
|
+
e.stopPropagation();
|
|
567
|
+
handleHide();
|
|
568
|
+
}}
|
|
569
|
+
className="shrink-0 text-[var(--color-text-faint,#9ca3af)] hover:text-[var(--color-text,#0a0a0a)] transition-colors"
|
|
570
|
+
>
|
|
571
|
+
<EyeOff size={12} />
|
|
572
|
+
</button>
|
|
573
|
+
</div>
|
|
574
|
+
) : (
|
|
575
|
+
<span className="font-mono text-[11px] text-[var(--color-text-muted,#6b7280)] tracking-wider">
|
|
576
|
+
{copied ? 'Copied' : '\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022'}
|
|
577
|
+
</span>
|
|
578
|
+
)}
|
|
579
|
+
</div>
|
|
580
|
+
{actionsPosition === 'right' && sensitiveActions}
|
|
581
|
+
</div>
|
|
582
|
+
);
|
|
583
|
+
};
|