unlock-panel.tsx 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. "use client";
  2. /**
  3. * Shared unlock block — password + eye + clear locked/unlocked messaging.
  4. */
  5. import { useState } from "react";
  6. import { Button } from "@/components/ui/button";
  7. import { ErrorBanner } from "@/components/ui/error-banner";
  8. import { PasswordInput } from "@/components/ui/password-input";
  9. import { unlockVault, type UnlockedMavi } from "@/lib/mavi/storage";
  10. export function UnlockPanel({
  11. onUnlocked,
  12. title = "Unlock mavi",
  13. description = "Stays unlocked in this browser tab until you lock or close the tab.",
  14. submitLabel = "Unlock",
  15. autoFocus,
  16. }: {
  17. onUnlocked: (wallet: UnlockedMavi) => void;
  18. title?: string;
  19. description?: string;
  20. submitLabel?: string;
  21. autoFocus?: boolean;
  22. }) {
  23. const [password, setPassword] = useState("");
  24. const [busy, setBusy] = useState(false);
  25. const [error, setError] = useState<string | null>(null);
  26. async function submit() {
  27. setError(null);
  28. if (!password) {
  29. setError("Enter your password.");
  30. return;
  31. }
  32. setBusy(true);
  33. try {
  34. const u = await unlockVault(password);
  35. setPassword("");
  36. onUnlocked(u);
  37. } catch (e) {
  38. setError(
  39. e instanceof Error
  40. ? e.message
  41. : "Wrong password or damaged vault. Try again.",
  42. );
  43. } finally {
  44. setBusy(false);
  45. }
  46. }
  47. return (
  48. <div className="space-y-3">
  49. <div>
  50. <p className="text-sm font-medium text-foreground">{title}</p>
  51. <p className="mt-1 text-xs text-muted-foreground leading-relaxed">
  52. {description}
  53. </p>
  54. </div>
  55. {error ? <ErrorBanner title="Could not unlock">{error}</ErrorBanner> : null}
  56. <PasswordInput
  57. id="mavi-unlock-shared"
  58. label="Password"
  59. value={password}
  60. onChange={setPassword}
  61. onEnter={() => void submit()}
  62. placeholder="Vault password"
  63. disabled={busy}
  64. />
  65. <Button
  66. type="button"
  67. className="w-full"
  68. disabled={busy || password.length < 1}
  69. onClick={() => void submit()}
  70. autoFocus={autoFocus}
  71. >
  72. {busy ? "Unlocking…" : submitLabel}
  73. </Button>
  74. </div>
  75. );
  76. }