Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
223 changes: 128 additions & 95 deletions app/chat/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import {
Phone,
Video,
MoreVertical,
Paperclip,
Smile,
Star,
} from "lucide-react";
import { calculateReputation, trackActivity } from "@/lib/reputation";
Expand Down Expand Up @@ -60,17 +62,50 @@ export default function ChatPage() {
const [selectedChatId, setSelectedChatId] = useState<string | null>(null);
const [query, setQuery] = useState("");
const [inputMessage, setInputMessage] = useState("");
const [roomMembersOpen, setRoomMembersOpen] = useState(false);
const [walletConnected, setWalletConnected] = useState(false);
const [roomMembersOpen, setRoomMembersOpen] = useState(false);
const [currentPublicKey, setCurrentPublicKey] = useState<string | null>(null);
const [reputationScore, setReputationScore] = useState(0);
const bottomRef = useRef<HTMLDivElement>(null);

const [messagesByChat, setMessagesByChat] = useState<Record<string, ChatMessage[]>>({
const [chats, setChats] = useState<ChatPreview[]>([
{
id: "1",
name: "Anon Whisper",
address: "GABC...1234",
lastMessage: "Got your message, will reply soon.",
lastSeen: "Today - 14:32",
unreadCount: 2,
status: "online",
},
{
id: "2",
name: "Room #xf23",
address: "GCDE...5678",
lastMessage: "Pinned the latest proposal for review.",
lastSeen: "Today - 09:10",
unreadCount: 0,
status: "recently_active",
},
{
id: "3",
name: "Collector",
address: "GHJK...9012",
lastMessage: "Let's sync tomorrow.",
lastSeen: "Yesterday - 18:04",
unreadCount: 0,
status: "offline",
},
]);

const [messagesByChat, setMessagesByChat] = useState<
Record<string, ChatMessage[]>
>({
"1": [
{
id: "m1",
author: "them",
text: "Hey, welcome to AnonChat 👋",
text: "Hey, welcome to AnonChat!",
time: "14:20",
delivered: true,
read: true,
Expand Down Expand Up @@ -114,7 +149,7 @@ export default function ChatPage() {
{
id: "m3",
author: "them",
text: "Messages stay end‑to‑end encrypted here.",
text: "Messages stay end-to-end encrypted here.",
time: "14:25",
delivered: true,
read: false,
Expand All @@ -134,85 +169,69 @@ export default function ChatPage() {
{
id: "m5",
author: "me",
text: "Lets catch up on the drop.",
text: "Let's catch up on the drop.",
time: "17:40",
delivered: true,
read: true,
},
],
})
});

// Update reputation score
// Auto-scroll to latest message
useEffect(() => {
const updateScore = () => {
setReputationScore(calculateReputation(currentPublicKey))
}
updateScore()
window.addEventListener("reputationUpdate", updateScore)
return () => window.removeEventListener("reputationUpdate", updateScore)
}, [currentPublicKey])
bottomRef.current?.scrollIntoView({ behavior: "smooth" });
}, [messagesByChat, selectedChatId]);

// Sync wallet state properly
// Sync wallet state
useEffect(() => {
const checkWallet = async () => {
const address = await getPublicKey()
setWalletConnected(!!address)
setCurrentPublicKey(address ?? null)
}
checkWallet()
const address = await getPublicKey();
setWalletConnected(!!address);
if (address) setCurrentPublicKey(address);
};
checkWallet();
const unsubscribe = onDisconnect(() => {
setWalletConnected(false)
setCurrentPublicKey(null)
})
const interval = setInterval(checkWallet, 1000)

setWalletConnected(false);
setCurrentPublicKey(null);
});
const interval = setInterval(checkWallet, 3000);
return () => {
unsubscribe()
clearInterval(interval)
}
}, [])
unsubscribe();
clearInterval(interval);
};
}, []);

const [chats, setChats] = useState<ChatPreview[]>([
{
id: "1",
name: "Anon Whisper",
address: "GABC ... 1234",
lastMessage: "Got your message, will reply soon.",
lastSeen: "Today • 14:32",
unreadCount: 2,
status: "online",
},
{
id: "2",
name: "Room #xf23",
address: "GCDE ... 5678",
lastMessage: "Pinned the latest proposal for review.",
lastSeen: "Today • 09:10",
unreadCount: 0,
status: "recently_active",
},
{
id: "3",
name: "Collector",
address: "GHJK ... 9012",
lastMessage: "Let’s sync tomorrow.",
lastSeen: "Yesterday • 18:04",
unreadCount: 0,
status: "offline",
},
]);
// Update reputation score
useEffect(() => {
const updateScore = () =>
setReputationScore(calculateReputation(currentPublicKey));
updateScore();
window.addEventListener("reputationUpdate", updateScore);
return () => window.removeEventListener("reputationUpdate", updateScore);
}, [currentPublicKey]);

// Listen for new room creation
useEffect(() => {
const handleRoomCreated = (e: any) => {
const newRoom = e.detail;
setChats((prev) => [newRoom, ...prev]);
setSelectedChatId(newRoom.id);
};
window.addEventListener("roomCreated", handleRoomCreated);
return () => window.removeEventListener("roomCreated", handleRoomCreated);
}, []);

const markRoomRead = async (roomId: string) => {
try {
await fetch("/api/rooms/mark-read", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ roomId }),
})
});
} catch (err) {
console.error("Failed to mark room read", err)
console.error("Failed to mark room read", err);
}
}
};

const handleSelectChat = async (id: string) => {
setSelectedChatId(id)
Expand Down Expand Up @@ -240,33 +259,41 @@ export default function ChatPage() {
id: `m${Date.now()}`,
author: "me",
text: inputMessage,
time: new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', hour12: false }),
time: new Date().toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
hour12: false,
}),
delivered: false,
read: false,
status: "sent"
}

setMessagesByChat(prev => ({
status: "sent",
};
setMessagesByChat((prev) => ({
...prev,
[selectedChatId]: [...(prev[selectedChatId] || []), newMessage]
}))

setChats(prev => prev.map(chat =>
chat.id === selectedChatId
? { ...chat, lastMessage: inputMessage, lastSeen: "Just now", unreadCount: 0 }
: chat
))

setInputMessage("")
trackActivity(currentPublicKey, 'message')
}
[selectedChatId]: [...(prev[selectedChatId] || []), newMessage],
}));
setChats((prev) =>
prev.map((chat) =>
chat.id === selectedChatId
? {
...chat,
lastMessage: inputMessage,
lastSeen: "Just now",
unreadCount: 0,
}
: chat,
),
);
setInputMessage("");
trackActivity(currentPublicKey, "message");
};

const handleKeyPress = (e: React.KeyboardEvent) => {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault()
handleSendMessage()
e.preventDefault();
handleSendMessage();
}
}
};

const getDeliveryStatus = (message: ChatMessage) => {
if (message.status) return message.status;
Expand Down Expand Up @@ -345,7 +372,6 @@ export default function ChatPage() {
return (
<div className="min-h-screen bg-background flex flex-col">
<Header />

<main className="flex-1 pt-24 pb-8 px-2 sm:px-4 lg:px-8 flex justify-center">
<div className="w-full max-w-6xl h-[min(82vh,760px)] bg-card border border-border/60 rounded-2xl shadow-lg overflow-hidden flex">
{/* Sidebar */}
Expand All @@ -366,7 +392,7 @@ export default function ChatPage() {
AnonChat
</div>
<div className="text-[11px] text-muted-foreground">
End‑to‑end encrypted
End-to-end encrypted
</div>
</div>
</div>
Expand All @@ -376,7 +402,7 @@ export default function ChatPage() {
</div>

{walletConnected && (
<div className="px-4 py-3 border-b border-border/60 flex items-center justify-between bg-[#12121a] gap-3">
<div className="px-4 py-3 border-b border-border/60 flex items-center justify-between bg-card gap-3">
<div className="inline-flex items-center gap-2 text-xs text-muted-foreground">
<div className="h-7 w-7 rounded-lg bg-primary/20 flex items-center justify-center text-primary">
<Wallet className="h-3.5 w-3.5" />
Expand All @@ -386,21 +412,24 @@ export default function ChatPage() {
CONNECTED
</span>
<span className="text-[11px] font-mono text-foreground">
{currentPublicKey ? `${currentPublicKey.slice(0, 4)} ... ${currentPublicKey.slice(-4)}` : "None"}
{currentPublicKey
? `${currentPublicKey.slice(0, 4)}...${currentPublicKey.slice(-4)}`
: "None"}
</span>
</div>
</div>

{CONFIG.EXPERIMENTAL_REPUTATION_ENABLED && (
<div className="flex items-center gap-1.5 px-2 py-1 rounded-lg bg-primary/10 border border-primary/20" title="Reputation Score (Experimental)">
<div
className="flex items-center gap-1.5 px-2 py-1 rounded-lg bg-primary/10 border border-primary/20"
title="Reputation Score"
>
<Star className="h-3 w-3 text-primary fill-primary" />
<span className="text-[11px] font-bold text-primary">
{reputationScore}
</span>
</div>
)}

<button className="inline-flex items-center gap-1 text-[11px] font-medium px-2.5 py-1.5 rounded-md bg-[#1b1b24] hover:bg-[#232330] transition border border-border/60">
<button className="inline-flex items-center gap-1 text-[11px] font-medium px-2.5 py-1.5 rounded-md hover:bg-muted/60 transition border border-border/60">
<Share2 className="h-3 w-3" />
<span>Share</span>
</button>
Expand Down Expand Up @@ -433,7 +462,7 @@ export default function ChatPage() {
) : (
<ul className="py-1">
{filteredChats.map((chat) => {
const isSelected = chat.id === selectedChatId
const isSelected = chat.id === selectedChatId;
return (
<li key={chat.id}>
<button
Expand Down Expand Up @@ -466,9 +495,14 @@ export default function ChatPage() {
{chat.lastMessage}
</p>
</div>
{chat.unreadCount > 0 && (
<span className="ml-auto text-[10px] font-bold bg-primary text-primary-foreground rounded-full px-1.5 py-0.5">
{chat.unreadCount}
</span>
)}
</button>
</li>
)
);
})}
</ul>
)}
Expand Down Expand Up @@ -500,7 +534,6 @@ export default function ChatPage() {
</div>
)}

{/* Conversation view */}
{selectedChat && (
<>
{/* Header */}
Expand Down Expand Up @@ -553,7 +586,7 @@ export default function ChatPage() {
<button
type="button"
className="inline-flex h-9 w-9 items-center justify-center rounded-full hover:bg-muted/60 transition"
aria-label="Room members and voting"
aria-label="Room members"
>
<MoreVertical className="h-4 w-4" />
</button>
Expand Down Expand Up @@ -619,6 +652,7 @@ export default function ChatPage() {
</div>
);
})}
<div ref={bottomRef} />
</div>

{/* Typing indicator */}
Expand Down Expand Up @@ -647,8 +681,7 @@ export default function ChatPage() {
</section>
</div>
</main>

<Footer />
</div>
)
);
}
Loading