Harden onion boot flow and deepen site surfaces
Add persistent onion key backup and restore, improve startup resilience, and flesh out the major site verticals with richer navigation, search coverage, and operator documentation. Made-with: Cursor
This commit is contained in:
156
components/ChatWidget.tsx
Normal file
156
components/ChatWidget.tsx
Normal file
@@ -0,0 +1,156 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
|
||||
const ChatWidget = () => {
|
||||
const [messages, setMessages] = useState([
|
||||
{ id: 1, sender: "System", text: "Welcome to the encrypted channel. All messages are end‑to‑end encrypted.", time: "12:00" },
|
||||
{ id: 2, sender: "Ghost", text: "Has anyone tested the new stimulant batch?", time: "12:05" },
|
||||
{ id: 3, sender: "Vendor_X", text: "Batch #8 passes all purity tests. Available for bulk.", time: "12:07" },
|
||||
{ id: 4, sender: "Anonymous", text: "Need a EU passport within 48 hours. DM if you can deliver.", time: "12:10" },
|
||||
]);
|
||||
const [input, setInput] = useState("");
|
||||
/** Set after mount so SSR HTML matches first client paint (no Math.random in initial state). */
|
||||
const [encryptionKey, setEncryptionKey] = useState("");
|
||||
const chatContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setEncryptionKey("session_" + Math.random().toString(36).substring(2, 9));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatContainerRef.current) {
|
||||
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
|
||||
}
|
||||
}, [messages]);
|
||||
|
||||
const handleSend = () => {
|
||||
if (!input.trim()) return;
|
||||
const newMessage = {
|
||||
id: messages.length + 1,
|
||||
sender: "You",
|
||||
text: input,
|
||||
time: new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
|
||||
};
|
||||
setMessages([...messages, newMessage]);
|
||||
setInput("");
|
||||
|
||||
// Simulate a reply after a delay
|
||||
setTimeout(() => {
|
||||
const replies = [
|
||||
"Received. Encryption verified.",
|
||||
"I can help with that. Check your vault.",
|
||||
"New drop incoming in 24 hours.",
|
||||
"Your request has been logged.",
|
||||
];
|
||||
const randomReply = replies[Math.floor(Math.random() * replies.length)];
|
||||
const replyMessage = {
|
||||
id: messages.length + 2,
|
||||
sender: "System",
|
||||
text: randomReply,
|
||||
time: new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }),
|
||||
};
|
||||
setMessages(prev => [...prev, replyMessage]);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
const handleEncrypt = () => {
|
||||
alert(`Session key: ${encryptionKey}\nAll messages are encrypted with this key.`);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="glass rounded-3xl border border-white/10 p-8">
|
||||
<div className="mb-8 flex items-center justify-between">
|
||||
<div>
|
||||
<h2 className="font-orbitron text-3xl font-bold">ENCRYPTED CHAT</h2>
|
||||
<p className="text-foreground/60">End‑to‑end encrypted messaging. No logs, no traces.</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={handleEncrypt}
|
||||
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-6 py-3 font-bold text-background"
|
||||
>
|
||||
🔐 SHOW SESSION KEY
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Chat messages */}
|
||||
<div
|
||||
ref={chatContainerRef}
|
||||
className="glass mb-6 h-96 overflow-y-auto rounded-2xl border border-white/10 p-6"
|
||||
>
|
||||
{messages.map((msg) => (
|
||||
<div
|
||||
key={msg.id}
|
||||
className={`mb-4 ${msg.sender === "You" ? "text-right" : ""}`}
|
||||
>
|
||||
<div className="mb-1 flex items-center gap-2">
|
||||
<span className={`inline-block rounded-full px-3 py-1 text-xs font-medium ${msg.sender === "You"
|
||||
? "bg-neon-cyan/20 text-neon-cyan"
|
||||
: msg.sender === "System"
|
||||
? "bg-neon-purple/20 text-neon-purple"
|
||||
: "bg-white/10"
|
||||
}`}>
|
||||
{msg.sender}
|
||||
</span>
|
||||
<span className="text-xs text-foreground/40">{msg.time}</span>
|
||||
</div>
|
||||
<div className={`glass inline-block max-w-[80%] rounded-2xl px-4 py-3 ${msg.sender === "You"
|
||||
? "bg-gradient-to-r from-neon-cyan/20 to-neon-cyan/10"
|
||||
: "bg-white/5"
|
||||
}`}>
|
||||
{msg.text}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Input area */}
|
||||
<div className="flex gap-4">
|
||||
<input
|
||||
type="text"
|
||||
className="glass flex-1 rounded-2xl border border-white/10 bg-transparent px-6 py-4"
|
||||
placeholder="Type your encrypted message..."
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={(e) => e.key === "Enter" && handleSend()}
|
||||
/>
|
||||
<button
|
||||
onClick={handleSend}
|
||||
className="rounded-full bg-gradient-to-r from-neon-cyan to-neon-purple px-8 py-4 font-bold text-background"
|
||||
>
|
||||
SEND
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Chat features */}
|
||||
<div className="mt-8 grid grid-cols-2 gap-6 md:grid-cols-4">
|
||||
<div className="glass rounded-2xl border border-white/10 p-4 text-center">
|
||||
<div className="text-2xl">🔒</div>
|
||||
<div className="mt-2 text-sm font-bold">AES‑256‑GCM</div>
|
||||
<div className="text-xs text-foreground/60">Encryption</div>
|
||||
</div>
|
||||
<div className="glass rounded-2xl border border-white/10 p-4 text-center">
|
||||
<div className="text-2xl">⚡</div>
|
||||
<div className="mt-2 text-sm font-bold">Zero‑Knowledge</div>
|
||||
<div className="text-xs text-foreground/60">No server storage</div>
|
||||
</div>
|
||||
<div className="glass rounded-2xl border border-white/10 p-4 text-center">
|
||||
<div className="text-2xl">🌐</div>
|
||||
<div className="mt-2 text-sm font-bold">WebRTC</div>
|
||||
<div className="text-xs text-foreground/60">P2P possible</div>
|
||||
</div>
|
||||
<div className="glass rounded-2xl border border-white/10 p-4 text-center">
|
||||
<div className="text-2xl">🕶️</div>
|
||||
<div className="mt-2 text-sm font-bold">Self‑Destruct</div>
|
||||
<div className="text-xs text-foreground/60">Messages expire</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 text-center text-xs text-foreground/40">
|
||||
<p>Ephemeral client-side queue — nothing leaves this browser profile until a real transport is configured.</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatWidget;
|
||||
Reference in New Issue
Block a user