-
-
Notifications
You must be signed in to change notification settings - Fork 22
fix(chat): show username instead of invalid date, Anonymous instead of dash #45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -1,5 +1,11 @@ | ||||||
| import { useState, useEffect, useRef, useCallback } from 'react'; | ||||||
| import { useWebSocket } from '../hooks/useWebSocket'; | ||||||
| import { | ||||||
| getMessageStats, | ||||||
| getWarningMessage, | ||||||
| isSendButtonDisabled, | ||||||
| validateMessage, | ||||||
| } from '../lib/messageValidation'; | ||||||
|
|
||||||
| interface Message { | ||||||
| id?: string; | ||||||
|
|
@@ -28,8 +34,13 @@ export default function ChatRoom({ room, currentUser, onClose }: ChatRoomProps) | |||||
| const [messages, setMessages] = useState<Message[]>([]); | ||||||
| const [inputValue, setInputValue] = useState(''); | ||||||
| const [showCrisisAlert, setShowCrisisAlert] = useState(false); | ||||||
| const [validationError, setValidationError] = useState(''); | ||||||
| const messagesEndRef = useRef<HTMLDivElement>(null); | ||||||
|
|
||||||
| const stats = getMessageStats(inputValue); | ||||||
| const warningMessage = getWarningMessage(inputValue); | ||||||
| const sendDisabled = isSendButtonDisabled(inputValue); | ||||||
|
|
||||||
| const handleMessage = useCallback((message: any) => { | ||||||
| if (message.type === 'history') { | ||||||
| // Load message history | ||||||
|
|
@@ -79,6 +90,8 @@ export default function ChatRoom({ room, currentUser, onClose }: ChatRoomProps) | |||||
| } else if (message.type === 'crisis_alert') { | ||||||
| setShowCrisisAlert(true); | ||||||
| setTimeout(() => setShowCrisisAlert(false), 10000); | ||||||
| } else if (message.type === 'error' && message.message) { | ||||||
| setValidationError(message.message); | ||||||
| } | ||||||
| }, []); | ||||||
|
|
||||||
|
|
@@ -102,8 +115,13 @@ export default function ChatRoom({ room, currentUser, onClose }: ChatRoomProps) | |||||
|
|
||||||
| const handleSendMessage = (e: React.FormEvent) => { | ||||||
| e.preventDefault(); | ||||||
| setValidationError(''); | ||||||
|
|
||||||
| if (!isConnected) return; | ||||||
|
|
||||||
| if (!inputValue.trim() || !isConnected) { | ||||||
| const result = validateMessage(inputValue); | ||||||
| if (!result.isValid) { | ||||||
| setValidationError(result.error || 'Invalid message'); | ||||||
| return; | ||||||
| } | ||||||
|
|
||||||
|
|
@@ -210,7 +228,7 @@ export default function ChatRoom({ room, currentUser, onClose }: ChatRoomProps) | |||||
| : 'text-gray-600' | ||||||
| }`} | ||||||
| > | ||||||
| {msg.userId === currentUser.id ? 'You' : msg.nickname} | ||||||
| {msg.userId === currentUser.id ? 'You' : (msg.nickname || 'Anonymous')} | ||||||
|
||||||
| </span> | ||||||
| <span | ||||||
| className={`text-xs ${ | ||||||
|
|
@@ -221,10 +239,12 @@ export default function ChatRoom({ room, currentUser, onClose }: ChatRoomProps) | |||||
| : 'text-gray-400' | ||||||
| }`} | ||||||
| > | ||||||
| {new Date(msg.timestamp).toLocaleTimeString([], { | ||||||
| hour: '2-digit', | ||||||
| minute: '2-digit', | ||||||
| })} | ||||||
| {(() => { | ||||||
| const d = new Date(msg.timestamp); | ||||||
| const isValid = !Number.isNaN(d.getTime()); | ||||||
| if (isValid) return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); | ||||||
| return msg.userId === currentUser.id ? 'You' : (msg.nickname || 'Anonymous'); | ||||||
|
||||||
| return msg.userId === currentUser.id ? 'You' : (msg.nickname || 'Anonymous'); | |
| return '-'; |
Copilot
AI
Mar 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The <input> element has a hardcoded maxLength={500} attribute (line 289), while the character counter displays stats.maxLength (line 308) sourced from the missing messageValidation module. If messageValidation.getMessageStats uses a different maximum length, the browser-enforced maxLength on the input and the displayed counter will be inconsistent — the user could see a counter limit that doesn't match the actual character cap. Once the messageValidation module is created, ensure that stats.maxLength and the maxLength attribute agree, or derive the maxLength prop dynamically from stats.maxLength.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| // Message Validation Utility for OpenMindWell (issue #15) | ||
| // Handles input validation, character limits, and user feedback | ||
|
|
||
| const MAX_MESSAGE_LENGTH = 500; | ||
| const WARNING_THRESHOLD_PERCENT = 80; | ||
|
|
||
| export interface MessageValidationResult { | ||
| isValid: boolean; | ||
| error?: string; | ||
| characterCount: number; | ||
| remainingCharacters: number; | ||
| isNearLimit: boolean; | ||
| } | ||
|
|
||
| /** | ||
| * Validates a chat message for sending | ||
| */ | ||
| export const validateMessage = (message: string): MessageValidationResult => { | ||
| const trimmedMessage = message.trim(); | ||
|
|
||
| if (!trimmedMessage || trimmedMessage.length === 0) { | ||
| return { | ||
| isValid: false, | ||
| error: 'Message cannot be empty', | ||
| characterCount: 0, | ||
| remainingCharacters: MAX_MESSAGE_LENGTH, | ||
| isNearLimit: false, | ||
| }; | ||
| } | ||
|
|
||
| if (trimmedMessage.length > MAX_MESSAGE_LENGTH) { | ||
| return { | ||
| isValid: false, | ||
| error: `Message exceeds maximum length of ${MAX_MESSAGE_LENGTH} characters`, | ||
| characterCount: trimmedMessage.length, | ||
| remainingCharacters: 0, | ||
| isNearLimit: true, | ||
| }; | ||
| } | ||
|
|
||
| const isNearLimit = | ||
| (trimmedMessage.length / MAX_MESSAGE_LENGTH) * 100 >= WARNING_THRESHOLD_PERCENT; | ||
|
|
||
| return { | ||
| isValid: true, | ||
| characterCount: trimmedMessage.length, | ||
| remainingCharacters: MAX_MESSAGE_LENGTH - trimmedMessage.length, | ||
| isNearLimit, | ||
| }; | ||
| }; | ||
|
|
||
| /** | ||
| * Gets character count and stats for display | ||
| */ | ||
| export const getMessageStats = (message: string) => { | ||
| const trimmedMessage = message.trim(); | ||
| const characterCount = trimmedMessage.length; | ||
| const remainingCharacters = MAX_MESSAGE_LENGTH - characterCount; | ||
| const percentUsed = (characterCount / MAX_MESSAGE_LENGTH) * 100; | ||
|
|
||
| return { | ||
| characterCount, | ||
| remainingCharacters, | ||
| percentUsed: Math.round(percentUsed), | ||
| maxLength: MAX_MESSAGE_LENGTH, | ||
| isNearLimit: percentUsed >= WARNING_THRESHOLD_PERCENT, | ||
| }; | ||
| }; | ||
|
|
||
| /** | ||
| * Whether the send button should be disabled | ||
| */ | ||
| export const isSendButtonDisabled = (message: string): boolean => { | ||
| const trimmedMessage = message.trim(); | ||
| return trimmedMessage.length === 0 || trimmedMessage.length > MAX_MESSAGE_LENGTH; | ||
| }; | ||
|
|
||
| /** | ||
| * Warning message when approaching or at limit | ||
| */ | ||
| export const getWarningMessage = (message: string): string => { | ||
| const stats = getMessageStats(message); | ||
|
|
||
| if (stats.percentUsed >= 95) { | ||
| return `Almost at limit: ${stats.remainingCharacters} characters remaining`; | ||
| } | ||
|
|
||
| if (stats.isNearLimit) { | ||
| return `${stats.remainingCharacters} characters remaining`; | ||
| } | ||
|
|
||
| return ''; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The file
frontend/src/lib/messageValidation.ts(or.js) is imported on lines 3–8 but does not exist in the repository. All four functions used —getMessageStats,getWarningMessage,isSendButtonDisabled, andvalidateMessage— are sourced from this missing module. This will cause a compile-time error (TypeScript/Vite will fail to resolve the module) and the chat UI will not load at all. The missing file must be created and exported before this PR can be merged.