diff --git a/OnlyPrompt.Frontend/chats.html b/OnlyPrompt.Frontend/chats.html
index 15ebe1f..48f3145 100644
--- a/OnlyPrompt.Frontend/chats.html
+++ b/OnlyPrompt.Frontend/chats.html
@@ -34,113 +34,42 @@
-
-
-
-
-
-
Alex Chen
-
- Hey Sarah! Really loved your last video on minimalism...
-
-
- 10:17 AM
-
-
-
-
-
-
Mia Wong
-
- Thanks for the prompt tips! They worked perfectly.
-
-
- Yesterday
-
-
-
-
-
-
Tom Rivera
-
- Let's schedule a call for the collab?
-
-
- Yesterday
-
+
+
-
+
-
-
-
-
- Hey Sarah! Really loved your last video on minimalism. Quick
- question about your workspace layout?
-
-
10:15 AM
-
-
-
-
- Thanks Alex! Appreciate it. Yes, happy to share! The desk is
- from Article, and the shelving unit is custom-built. Highly
- recommend a clean setup!
-
-
10:16 AM
-
-
-
-
- Thanks so much! Your aesthetic is exactly what I'm aiming
- for. Can't wait for your next piece!
-
-
10:17 AM
-
-
-
-
- Awesome! Let me know if you need more tips. Enjoy the
- process! 😊
-
-
10:18 AM
-
-
-
@@ -175,6 +104,278 @@
(data) =>
(document.getElementById("topbar-container").innerHTML = data),
);
+
+ const STORAGE_KEY = "onlyprompt-chat-conversations";
+ const chatList = document.getElementById("chatList");
+ const chatMessages = document.getElementById("chatMessages");
+ const chatForm = document.getElementById("chatForm");
+ const messageInput = document.getElementById("messageInput");
+ const activeChatAvatar = document.getElementById("activeChatAvatar");
+ const activeChatName = document.getElementById("activeChatName");
+ const activeChatStatus = document.getElementById("activeChatStatus");
+ const newChatBtn = document.getElementById("newChatBtn");
+ const newChatPanel = document.getElementById("new-chat-panel");
+ const creatorSearch = document.getElementById("creatorSearch");
+ const creatorSearchResults = document.getElementById("creatorSearchResults");
+
+ let conversations = loadConversations();
+ let creators = [];
+ let activeConversationId = null;
+
+ function escapeHtml(value) {
+ return String(value ?? "")
+ .replaceAll("&", "&")
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll('"', """)
+ .replaceAll("'", "'");
+ }
+
+ function loadConversations() {
+ try {
+ const saved = JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]");
+ return Array.isArray(saved) && saved.length ? saved : demoConversations();
+ } catch {
+ return demoConversations();
+ }
+ }
+
+ function saveConversations() {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(conversations));
+ }
+
+ function demoConversations() {
+ const now = Date.now();
+ return [
+ {
+ id: "demo-alex",
+ userId: "demo-alex",
+ name: "Alex Chen",
+ avatar: "../images/content/creator2.png",
+ updatedAt: now - 60000,
+ messages: [
+ { from: "them", text: "Hey, I liked your last prompt idea.", createdAt: now - 180000 },
+ { from: "me", text: "Thanks. Which part was useful?", createdAt: now - 120000 },
+ { from: "them", text: "The structure was easy to adapt.", createdAt: now - 60000 },
+ ],
+ },
+ {
+ id: "demo-mia",
+ userId: "demo-mia",
+ name: "Mia Wong",
+ avatar: "../images/content/creator3.png",
+ updatedAt: now - 86400000,
+ messages: [
+ { from: "them", text: "Thanks for the prompt tips.", createdAt: now - 86400000 },
+ ],
+ },
+ ];
+ }
+
+ function formatTime(timestamp) {
+ const date = new Date(timestamp);
+ const today = new Date();
+ if (date.toDateString() === today.toDateString()) {
+ return date.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
+ }
+ return date.toLocaleDateString([], { month: "short", day: "numeric" });
+ }
+
+ function getLastMessage(conversation) {
+ return conversation.messages.at(-1)?.text || "No messages yet.";
+ }
+
+ function renderChatList() {
+ conversations.sort((a, b) => b.updatedAt - a.updatedAt);
+ if (!conversations.length) {
+ chatList.innerHTML = '
Start a chat with a creator.
';
+ return;
+ }
+
+ chatList.innerHTML = conversations
+ .map((conversation) => `
+
+
+
+
${escapeHtml(conversation.name)}
+
${escapeHtml(getLastMessage(conversation))}
+
+ ${formatTime(conversation.updatedAt)}
+
+ `)
+ .join("");
+
+ chatList.querySelectorAll("[data-chat-id]").forEach((button) => {
+ button.addEventListener("click", () => selectConversation(button.dataset.chatId));
+ });
+ }
+
+ function renderMessages() {
+ const conversation = conversations.find((item) => item.id === activeConversationId);
+ if (!conversation) {
+ activeChatAvatar.src = "../images/content/cat.png";
+ activeChatName.textContent = "Select a chat";
+ activeChatStatus.textContent = "Ready";
+ chatMessages.innerHTML = '
Choose a conversation or start a new chat.
';
+ messageInput.disabled = true;
+ chatForm.querySelector("button").disabled = true;
+ return;
+ }
+
+ activeChatAvatar.src = conversation.avatar;
+ activeChatName.textContent = conversation.name;
+ activeChatStatus.textContent = "Online";
+ chatMessages.setAttribute("aria-label", `Conversation with ${conversation.name}`);
+ messageInput.disabled = false;
+ chatForm.querySelector("button").disabled = false;
+
+ chatMessages.innerHTML = conversation.messages
+ .map((message) => `
+
+
${escapeHtml(message.text)}
+
${formatTime(message.createdAt)}
+
+ `)
+ .join("");
+ chatMessages.scrollTop = chatMessages.scrollHeight;
+ }
+
+ function selectConversation(id) {
+ activeConversationId = id;
+ renderChatList();
+ renderMessages();
+ }
+
+ function startConversation(creator) {
+ const id = `user-${creator.userId}`;
+ let conversation = conversations.find((item) => item.id === id);
+ if (!conversation) {
+ conversation = {
+ id,
+ userId: creator.userId,
+ name: creator.displayName,
+ avatar: creator.avatarUrl || "../images/content/cat.png",
+ updatedAt: Date.now(),
+ messages: [],
+ };
+ conversations.push(conversation);
+ saveConversations();
+ }
+ activeConversationId = id;
+ newChatPanel.hidden = true;
+ newChatBtn.setAttribute("aria-expanded", "false");
+ creatorSearch.value = "";
+ renderCreatorResults();
+ renderChatList();
+ renderMessages();
+ messageInput.focus();
+ }
+
+ function addMessage(text) {
+ const conversation = conversations.find((item) => item.id === activeConversationId);
+ if (!conversation || !text.trim()) return;
+
+ conversation.messages.push({
+ from: "me",
+ text: text.trim(),
+ createdAt: Date.now(),
+ });
+ conversation.updatedAt = Date.now();
+ saveConversations();
+ renderChatList();
+ renderMessages();
+ }
+
+ async function loadCreators() {
+ try {
+ const response = await fetch("/api/v1/profiles?limit=100", {
+ credentials: "same-origin",
+ });
+ if (!response.ok) throw new Error("Creators could not be loaded.");
+ creators = await response.json();
+ } catch {
+ creators = [
+ { userId: "demo-alex", displayName: "Alex Chen", slug: "alex", avatarUrl: "../images/content/creator2.png" },
+ { userId: "demo-mia", displayName: "Mia Wong", slug: "mia", avatarUrl: "../images/content/creator3.png" },
+ { userId: "demo-tom", displayName: "Tom Rivera", slug: "tom", avatarUrl: "../images/content/creator4.png" },
+ ];
+ }
+ renderCreatorResults();
+ }
+
+ function renderCreatorResults() {
+ const search = creatorSearch.value.trim().toLowerCase();
+ const results = creators
+ .filter((creator) =>
+ `${creator.displayName || ""} ${creator.slug || ""}`.toLowerCase().includes(search),
+ )
+ .slice(0, 8);
+
+ if (!results.length) {
+ creatorSearchResults.innerHTML = '
No creators found.
';
+ return;
+ }
+
+ creatorSearchResults.innerHTML = results
+ .map((creator) => `
+
+
+
+ ${escapeHtml(creator.displayName)}
+ @${escapeHtml(creator.slug || "creator")}
+
+
+ `)
+ .join("");
+
+ creatorSearchResults.querySelectorAll("[data-user-id]").forEach((button) => {
+ button.addEventListener("click", () => {
+ const creator = creators.find((item) => item.userId === button.dataset.userId);
+ if (creator) startConversation(creator);
+ });
+ });
+ }
+
+ function openConversationFromUrl() {
+ const params = new URLSearchParams(location.search);
+ const userId = params.get("userId");
+ if (!userId) return false;
+
+ startConversation({
+ userId,
+ displayName: params.get("name") || "Creator",
+ slug: "creator",
+ avatarUrl: params.get("avatar") || "../images/content/cat.png",
+ });
+ history.replaceState(null, "", "/chats.html");
+ return true;
+ }
+
+ newChatBtn.addEventListener("click", () => {
+ const willOpen = newChatPanel.hidden;
+ newChatPanel.hidden = !willOpen;
+ newChatBtn.setAttribute("aria-expanded", String(willOpen));
+ if (willOpen) creatorSearch.focus();
+ });
+
+ creatorSearch.addEventListener("input", renderCreatorResults);
+ chatForm.addEventListener("submit", (event) => {
+ event.preventDefault();
+ addMessage(messageInput.value);
+ messageInput.value = "";
+ });
+
+ loadCreators().then(() => {
+ if (!openConversationFromUrl()) {
+ activeConversationId = conversations[0]?.id || null;
+ renderChatList();
+ renderMessages();
+ }
+ });