| null>(null)
+
+ function hide() {
+ if (hideTimer.current) { clearTimeout(hideTimer.current); hideTimer.current = null }
+ setRevealed(false)
+ setValue(null)
+ setCopied(false)
+ }
async function reveal() {
- if (revealed) { setRevealed(false); setValue(null); return }
+ if (revealed) { hide(); return }
setLoading(true)
const result = await revealSecret(secret.id)
setLoading(false)
if (result.error) { toast.error(result.error); return }
setValue(result.value ?? '')
setRevealed(true)
+ if (hideTimer.current) clearTimeout(hideTimer.current)
+ hideTimer.current = setTimeout(hide, 30000)
}
+ async function copy() {
+ if (!value) return
+ try {
+ await navigator.clipboard.writeText(value)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 1500)
+ } catch {
+ toast.error('Could not copy to clipboard')
+ }
+ }
+
+ // Re-hide a revealed secret when the window loses focus (screen share /
+ // shoulder surfing), and drop the plaintext + timer on unmount.
+ useEffect(() => {
+ const onBlur = () => hide()
+ window.addEventListener('blur', onBlur)
+ return () => {
+ window.removeEventListener('blur', onBlur)
+ if (hideTimer.current) clearTimeout(hideTimer.current)
+ }
+ }, [])
+
async function handleDelete() {
if (!(await confirm({ title: 'Delete secret', description: `"${secret.title}" cannot be undone.`, confirmText: 'Delete', destructive: true }))) return
const result = await deleteSecret(secret.id)
@@ -388,7 +421,18 @@ function SecretRow({ secret, onDelete, canManageAcl, aclRoleOptions, aclInitial
{secret.title}
{secret.area && {secret.area}
}
{revealed && value && (
- {value}
+
+
{value}
+
+
)}
diff --git a/components/ops/ops-acl-editor.tsx b/components/ops/ops-acl-editor.tsx
index d8f6824..0e94521 100644
--- a/components/ops/ops-acl-editor.tsx
+++ b/components/ops/ops-acl-editor.tsx
@@ -2,6 +2,7 @@
import { useState } from 'react'
import { toast } from 'sonner'
+import { Check } from 'lucide-react'
import { setOpsAcl } from '@/lib/actions'
export interface AclRoleOption { value: string; label: string }
@@ -37,6 +38,8 @@ export function OpsAclEditor({ entityType, entityId, options, initial }: Props)
else back.add(value)
return back
})
+ } else {
+ toast.success('Access updated')
}
}
@@ -55,11 +58,13 @@ export function OpsAclEditor({ entityType, entityId, options, initial }: Props)
)