From 10592b76c79794183f0b748dd8c092dea2169733 Mon Sep 17 00:00:00 2001 From: Thuvaraka Yogarajah Date: Sun, 14 Jun 2026 12:07:01 +0200 Subject: [PATCH] Add basic creator chat flow --- OnlyPrompt.Frontend/chats.html | 381 ++++++++++++++++++++------ OnlyPrompt.Frontend/community.html | 21 +- OnlyPrompt.Frontend/css/chats.css | 84 ++++++ OnlyPrompt.Frontend/css/community.css | 29 +- 4 files changed, 416 insertions(+), 99 deletions(-) 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 @@

Messages

-
-
- - - - - - + +
- +
Alex Chen
-
Alex Chen
+
Select a chat
- Online + + Ready
-
- -
-
- 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) => ` + + `) + .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)}
+ +
+ `) + .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) => ` + + `) + .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(); + } + }); diff --git a/OnlyPrompt.Frontend/community.html b/OnlyPrompt.Frontend/community.html index 40b697b..c30f996 100644 --- a/OnlyPrompt.Frontend/community.html +++ b/OnlyPrompt.Frontend/community.html @@ -99,6 +99,7 @@ function renderCard(c) { const profileHref = `/profile?id=${encodeURIComponent(c.userId)}`; + const chatHref = `/chats.html?userId=${encodeURIComponent(c.userId)}&name=${encodeURIComponent(c.displayName)}&avatar=${encodeURIComponent(c.avatarUrl || "../images/content/cat.png")}`; return ` - +
`; } diff --git a/OnlyPrompt.Frontend/css/chats.css b/OnlyPrompt.Frontend/css/chats.css index cac30af..5fbefdb 100644 --- a/OnlyPrompt.Frontend/css/chats.css +++ b/OnlyPrompt.Frontend/css/chats.css @@ -42,9 +42,81 @@ .new-chat-btn { background: none; border: none; + border-radius: 999px; font-size: 1.2rem; color: #3b82f6; cursor: pointer; + padding: 8px; +} + +.new-chat-btn:hover, +.new-chat-btn[aria-expanded="true"] { + background: #eef2ff; +} + +.new-chat-panel { + border-bottom: 1px solid #eef2f7; + padding: 14px 16px; +} + +.new-chat-panel input { + width: 100%; + border: 1px solid #dbe2ea; + border-radius: 12px; + padding: 10px 12px; + font: inherit; +} + +.creator-search-results { + display: grid; + gap: 8px; + margin-top: 10px; + max-height: 260px; + overflow-y: auto; +} + +.creator-result { + align-items: center; + background: #f8fafc; + border: 1px solid transparent; + border-radius: 12px; + color: inherit; + cursor: pointer; + display: flex; + gap: 10px; + padding: 10px; + text-align: left; + width: 100%; +} + +.creator-result:hover, +.creator-result:focus-visible { + background: #eef2ff; + border-color: #c7d2fe; +} + +.creator-result img { + border-radius: 50%; + height: 36px; + object-fit: cover; + width: 36px; +} + +.creator-result span { + display: grid; +} + +.creator-result small, +.creator-result-empty, +.chat-empty { + color: #64748b; + font-size: 0.82rem; +} + +.creator-result-empty, +.chat-empty { + padding: 18px; + text-align: center; } .chat-list-items { @@ -195,6 +267,7 @@ padding: 16px 24px; border-top: 1px solid #eef2f7; background: #fff; + margin: 0; } .chat-input-area input { flex: 1; @@ -207,6 +280,12 @@ .chat-input-area input:focus { border-color: #3b82f6; } + +.chat-input-area input:disabled { + background: #f8fafc; + cursor: not-allowed; +} + .send-btn { background: var(--gradient); border: none; @@ -221,6 +300,11 @@ opacity: 0.85; } +.send-btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + /* Responsive */ @media (max-width: 768px) { .chats-main { diff --git a/OnlyPrompt.Frontend/css/community.css b/OnlyPrompt.Frontend/css/community.css index a4acd4a..f50a344 100644 --- a/OnlyPrompt.Frontend/css/community.css +++ b/OnlyPrompt.Frontend/css/community.css @@ -133,7 +133,14 @@ .creator-stats i { margin-right: 4px; } -.follow-btn { +.creator-actions { + display: flex; + gap: 10px; + flex-wrap: wrap; +} + +.follow-btn, +.creator-chat-btn { background: var(--gradient); color: white; border: none; @@ -142,11 +149,25 @@ font-size: 0.8rem; font-weight: 600; cursor: pointer; + display: inline-flex; + align-items: center; + gap: 6px; + justify-content: center; + min-height: 34px; + text-decoration: none; transition: opacity 0.2s; } -.follow-btn:hover { + +.follow-btn:hover, +.creator-chat-btn:hover { opacity: 0.85; } + +.creator-chat-btn { + background: #eef2ff; + color: #2563eb; +} + .follow-btn.following { background: transparent; border: 2px solid #94a3b8; @@ -187,6 +208,10 @@ .follow-btn { width: 100%; } + .creator-actions, + .creator-chat-btn { + width: 100%; + } } /* Star rating in creator cards */