From d4d99fb5e79bf45d67e31a509b4db60237441afd Mon Sep 17 00:00:00 2001 From: Juan Sebastian Montoya Date: Fri, 7 Mar 2025 01:06:31 -0500 Subject: [PATCH] feat: enhance chat room UI with user avatars and message improvements - Added user avatar generation with external avatar service - Implemented message styling with user-specific layouts - Added message length counter and validation - Updated CSS for improved message and user profile display - Restricted message length to 2048 characters - Added disabled state for send button based on message length --- apps/api/package.json | 6 +- apps/api/src/index.ts | 2 +- apps/web/src/App.css | 90 ++++++++++++++++++++++++--- apps/web/src/App.tsx | 17 ++++- apps/web/src/components/chat-room.tsx | 53 +++++++++++++--- 5 files changed, 144 insertions(+), 24 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index 2612f9c..edcfd48 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -10,9 +10,9 @@ "test": "ts-node --test test/**/*.test.ts", "start": "node dist/index.js", "dev": "nodemon --delay 2000ms src/index.ts", - "prisma:generate": "dotenv -e ../../.env -- prisma generate", - "prisma:migrate": "dotenv -e ../../.env -- prisma migrate dev", - "prisma:init": "dotenv -e ../../.env -- prisma migrate dev --name init", + "prisma:generate": "dotenv -e ../../.env.local -- prisma generate", + "prisma:migrate": "dotenv -e ../../.env.local -- prisma migrate dev", + "prisma:init": "dotenv -e ../../.env.local -- prisma migrate dev --name init", "build": "tsc" }, "keywords": [], diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts index c0c32ba..eb0e146 100644 --- a/apps/api/src/index.ts +++ b/apps/api/src/index.ts @@ -9,7 +9,7 @@ import { PrismaClient } from '@prisma/client'; import fastifyCors from '@fastify/cors'; import { z } from 'zod'; -dotenv.config({ path: '../../.env' }); +dotenv.config({ path: '../../.env.local' }); const { allowedOrigins, port, host } = z .object({ diff --git a/apps/web/src/App.css b/apps/web/src/App.css index a5d63a1..cdce3c4 100644 --- a/apps/web/src/App.css +++ b/apps/web/src/App.css @@ -79,6 +79,35 @@ body { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); } +.user-profile { + display: flex; + align-items: center; + gap: 1rem; +} + +.user-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; + border: 2px solid white; +} + +.logout-button { + padding: 0.5rem 1rem; + font-size: 0.875rem; + background-color: rgba(255, 255, 255, 0.2); + color: white; + border: none; + border-radius: 0.25rem; + cursor: pointer; + transition: background-color 0.2s; +} + +.logout-button:hover { + background-color: rgba(255, 255, 255, 0.3); +} + .app-main { flex: 1; padding: 1rem; @@ -180,11 +209,6 @@ button:disabled { border-radius: 0.25rem; } -.logout-button { - padding: 0.5rem 1rem; - font-size: 0.875rem; -} - /* Chat styles */ .chat-container { display: flex; @@ -357,21 +381,53 @@ button:disabled { .chat-messages { flex: 1; overflow-y: auto; - padding: 1rem; display: flex; flex-direction: column; gap: 0.75rem; + padding: 1rem; +} + +.message-wrapper { + display: flex; + align-items: flex-end; + max-width: 100%; + gap: 0.75rem; +} + +.message-avatar { + width: 40px; + height: 40px; + border-radius: 50%; + object-fit: cover; + align-self: flex-end; } .message { - max-width: 70%; + max-width: 85%; + min-width: 100px; padding: 0.75rem; border-radius: 0.5rem; + word-break: break-word; + line-height: 1.4; + width: fit-content; + display: inline-block; +} + +.other-message-wrapper { + align-self: flex-start; +} + +.own-message-wrapper { + align-self: flex-end; + flex-direction: row-reverse; +} + +.other-message { background-color: #f3f4f6; align-self: flex-start; } -.message.own-message { +.own-message { background-color: rgba(79, 70, 229, 0.1); align-self: flex-end; } @@ -385,18 +441,31 @@ button:disabled { .username { font-weight: 500; + margin-right: 0.5rem; } .time { color: var(--secondary-color); + opacity: 0.7; } .message-content { word-break: break-word; + white-space: pre-wrap; + display: inline-block; + text-align: justify; +} + +.message-length-counter { + font-size: 0.75rem; + color: var(--secondary-color); + margin-right: 0.5rem; + align-self: center; } .message-form { display: flex; + align-items: center; padding: 1rem; border-top: 1px solid var(--border-color); } @@ -413,6 +482,11 @@ button:disabled { border-radius: 0 0.25rem 0.25rem 0; } +.message-form button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + /* Create Room styles */ .create-room { padding: 1rem; diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx index 1bd5105..ee33b53 100644 --- a/apps/web/src/App.tsx +++ b/apps/web/src/App.tsx @@ -14,6 +14,10 @@ function App() { const [selectedRoomId, setSelectedRoomId] = createSignal(null); const [showRegister, setShowRegister] = createSignal(false); + // Function to generate avatar URL + const getUserAvatarUrl = (userId: string, size: number = 40) => + `https://avatars.jusemon.com/${userId}?size=${size}`; + // Check if user is already authenticated const checkAuth = () => { const token = localStorage.getItem('token'); @@ -55,9 +59,16 @@ function App() {

Unreal Chat

{isAuthenticated() && ( - + )}
diff --git a/apps/web/src/components/chat-room.tsx b/apps/web/src/components/chat-room.tsx index 8250620..d717d32 100644 --- a/apps/web/src/components/chat-room.tsx +++ b/apps/web/src/components/chat-room.tsx @@ -161,14 +161,18 @@ export function ChatRoom(props: ChatRoomProps) { } }); + const MAX_MESSAGE_LENGTH = 2048; + const handleSendMessage = async (e: Event) => { e.preventDefault(); - if (!message().trim()) return; + const trimmedMessage = message().trim(); + if (!trimmedMessage) return; + if (trimmedMessage.length > MAX_MESSAGE_LENGTH) return; try { await sendMessage({ - content: message(), + content: trimmedMessage, roomId: props.roomId, }); setMessage(''); @@ -242,6 +246,10 @@ export function ChatRoom(props: ChatRoomProps) { }; }); + // Function to generate avatar URL + const getUserAvatarUrl = (userId: string, size: number = 40) => + `https://avatars.jusemon.com/${userId}?size=${size}`; + return (
Loading room...
}> @@ -276,13 +284,24 @@ export function ChatRoom(props: ChatRoomProps) { {(message) => (
-
- {message.user.username} - {formatTime(message.createdAt)} + {message.user.id !== props.userId && ( + {`${message.user.username}'s + )} +
+
+ {message.user.username} + {formatTime(message.createdAt)} +
+
{message.content}
-
{message.content}
)} @@ -295,7 +314,12 @@ export function ChatRoom(props: ChatRoomProps) { ref={messageInput} type='text' value={message()} - onInput={(e) => setMessage(e.currentTarget.value)} + onInput={(e) => { + const inputValue = e.currentTarget.value; + if (inputValue.length <= MAX_MESSAGE_LENGTH) { + setMessage(inputValue); + } + }} placeholder='Type a message...' />
- +
+ {message().length}/{MAX_MESSAGE_LENGTH} +
+ {showEmojiPicker() && (