Skip to content
File

Blob: src/client/components/inbox/inbox-header.tsx

typescript123 lines
1import { useState } from "react";
2import { Clock, Copy, RefreshCw, Timer, Trash2, ShieldAlert } from "lucide-react";
3import { Button, Dialog } from "@/client/components/ui";
4import type { InboxInfo, TempMailboxTtlHours } from "@/client/lib/api";
5 
6const socketColors: Record<string, string> = {
7 open: "bg-emerald-500",
8 connecting: "bg-yellow-500 animate-pulse",
9 closed: "bg-zinc-500",
10 error: "bg-red-500",
11 idle: "bg-zinc-600",
12};
13 
14interface InboxHeaderProps {
15 address: string;
16 adminMode: boolean;
17 availableExtensions: TempMailboxTtlHours[];
18 countdown: string;
19 extendingTo: TempMailboxTtlHours | null;
20 inbox: InboxInfo | null;
21 isAdminInspectingTemporaryInbox: boolean;
22 socketState: "idle" | "connecting" | "open" | "closed" | "error";
23 onCopy: () => void | Promise<void>;
24 onDeleteInbox: () => void | Promise<void>;
25 onExtendInbox: (ttlHours: TempMailboxTtlHours) => void | Promise<void>;
26 onRefresh: () => void | Promise<void>;
27}
28 
29export function InboxHeader({
30 address,
31 adminMode,
32 availableExtensions,
33 countdown,
34 extendingTo,
35 inbox,
36 isAdminInspectingTemporaryInbox,
37 socketState,
38 onCopy,
39 onDeleteInbox,
40 onExtendInbox,
41 onRefresh,
42}: InboxHeaderProps) {
43 const [confirmDelete, setConfirmDelete] = useState(false);
44 
45 return (
46 <section className="flex flex-col gap-4 rounded-xl border border-zinc-800/60 bg-zinc-900 p-5 sm:flex-row sm:items-center sm:justify-between">
47 <div className="min-w-0">
48 <div className="flex items-center gap-2">
49 <span className="text-xs font-semibold uppercase tracking-wider text-accent-400">Live Inbox</span>
50 <span className={`h-1.5 w-1.5 rounded-full ${socketColors[socketState] ?? "bg-zinc-600"}`} />
51 <span className="text-xs text-zinc-400">{socketState}</span>
52 </div>
53 <h1 className="mt-1 truncate text-lg font-semibold text-zinc-100">{address}</h1>
54 <div className="mt-0.5 flex flex-wrap items-center gap-x-3 gap-y-1">
55 {inbox?.isPermanent ? (
56 <span className="text-sm text-zinc-400">Permanent inbox</span>
57 ) : countdown ? (
58 <span className="flex items-center gap-1.5 text-sm text-zinc-400">
59 <Timer className="h-3.5 w-3.5 text-accent-400" />
60 {countdown}
61 </span>
62 ) : (
63 <span className="text-sm text-zinc-400">Loading inbox details...</span>
64 )}
65 </div>
66 {isAdminInspectingTemporaryInbox ? (
67 <p className="mt-2 inline-flex items-center gap-2 rounded-full border border-amber-500/20 bg-amber-500/10 px-3 py-1 text-xs font-medium text-amber-300">
68 <ShieldAlert className="h-3 w-3" />
69 Admin inspection mode: mailbox contents are read-only, but you can delete this temporary inbox.
70 </p>
71 ) : null}
72 </div>
73
74 <div className="flex shrink-0 flex-wrap gap-2">
75 <Button icon={<Copy className="h-3.5 w-3.5" />} onClick={() => void onCopy()}>
76 Copy
77 </Button>
78 <Button icon={<RefreshCw className="h-3.5 w-3.5" />} onClick={() => void onRefresh()}>
79 Refresh
80 </Button>
81 {!adminMode &&
82 availableExtensions.map((ttlHours) => (
83 <button
84 key={ttlHours}
85 className="inline-flex items-center gap-1.5 rounded-lg border border-emerald-500/20 bg-emerald-500/10 px-3 py-1.5 text-xs font-medium text-emerald-300 transition-transform hover:bg-emerald-500/20 active:scale-[0.98] disabled:pointer-events-none disabled:opacity-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent-500/50 focus-visible:ring-offset-2 focus-visible:ring-offset-canvas"
86 type="button"
87 disabled={extendingTo !== null}
88 onClick={() => void onExtendInbox(ttlHours)}
89 >
90 <Clock className="h-3.5 w-3.5" />
91 {extendingTo === ttlHours ? "Extending..." : `Extend ${ttlHours}h`}
92 </button>
93 ))}
94 {inbox && !inbox.isPermanent ? (
95 <Button variant="danger" icon={<Trash2 className="h-3.5 w-3.5" />} onClick={() => setConfirmDelete(true)}>
96 {adminMode ? "Delete mailbox" : "Delete"}
97 </Button>
98 ) : null}
99 </div>
100
101 <Dialog open={confirmDelete} onClose={() => setConfirmDelete(false)} title="Delete this inbox?">
102 <p className="text-sm text-zinc-400">
103 This will permanently delete <strong className="text-zinc-200">{address}</strong> and all its emails. This
104 action cannot be undone.
105 </p>
106 <div className="mt-5 flex justify-end gap-3">
107 <Button onClick={() => setConfirmDelete(false)}>Cancel</Button>
108 <Button
109 variant="danger"
110 icon={<Trash2 className="h-3.5 w-3.5" />}
111 onClick={() => {
112 setConfirmDelete(false);
113 void onDeleteInbox();
114 }}
115 >
116 Delete permanently
117 </Button>
118 </div>
119 </Dialog>
120 </section>
121 );
122}