diff --git a/UI_UPDATE.MD b/UI_UPDATE.MD index efb55ca..7a06d9f 100644 --- a/UI_UPDATE.MD +++ b/UI_UPDATE.MD @@ -461,22 +461,14 @@ All utilities follow the established pattern of using CSS custom properties from ### Sub-task 4.2: Interactive States and Feedback #### 4.2.1: State Implementation -- [x] Design comprehensive hover states -- [x] Implement focus indicators meeting WCAG standards -- [x] Add active and pressed state styling +- [ ] Design comprehensive hover states +- [ ] Implement focus indicators meeting WCAG standards +- [ ] Add active and pressed state styling -#### 4.2.2: Form Validation - COMPLETED 9/29/2025 -- [x] Implement visual form validation feedback -- [x] Add error state styling -- [x] Create success confirmation states - -**Notes:** Comprehensive form validation system implemented for chat inputs including: -- HTML structure updates: Added form-group wrappers with error message elements for nickname and message inputs -- Enhanced JavaScript validation: Added client-side validation with detailed error messages for nickname (1-20 chars, alphanumeric/places) and message (1-1000 chars) requirements -- CSS integration: Utilizes existing .form-group--error and .form-group--success classes for visual feedback -- User experience: Error messages display below inputs, success states provide green styling and toast confirmation, focus management ensures good UX flow -- Validation coverage: Empty inputs, character length limits, nickname character restrictions, and server error handling -- Accessibility: Proper error messaging and focus management for screen readers and keyboard navigation +#### 4.2.2: Form Validation +- [ ] Implement visual form validation feedback +- [ ] Add error state styling +- [ ] Create success confirmation states ### Sub-task 4.3: Enhanced User Feedback Systems diff --git a/assets/js/chat.js b/assets/js/chat.js index 0991fb8..ec289b4 100644 --- a/assets/js/chat.js +++ b/assets/js/chat.js @@ -76,137 +76,44 @@ }); } - // Validation helper functions - function validateNickname(nickname) { - if (!nickname) { - return { valid: false, message: 'Nickname is required' }; - } - - const trimmed = nickname.trim(); - if (trimmed.length === 0) { - return { valid: false, message: 'Nickname cannot be empty' }; - } - - if (trimmed.length > 20) { - return { valid: false, message: 'Nickname must be 20 characters or less' }; - } - - if (trimmed.length < 1) { - return { valid: false, message: 'Nickname must be at least 1 character' }; - } - - // Allow alphanumeric characters and spaces, apostrophes, hyphens - const nicknameRegex = /^[a-zA-Z0-9\s'-]+$/; - if (!nicknameRegex.test(trimmed)) { - return { valid: false, message: 'Nickname can only contain letters, numbers, spaces, hyphens, and apostrophes' }; - } - - return { valid: true }; - } - - function validateMessage(message) { - if (!message) { - return { valid: false, message: 'Message is required' }; - } - - const trimmed = message.trim(); - if (trimmed.length === 0) { - return { valid: false, message: 'Message cannot be empty' }; - } - - if (trimmed.length > 1000) { - return { valid: false, message: 'Message must be 1000 characters or less' }; - } - - return { valid: true }; - } - - function updateValidationUI(field, errorElement, isValid, errorMessage) { - const formGroup = errorElement.closest('.form-group'); - const input = formGroup.querySelector('input'); - - formGroup.classList.remove('form-group--error', 'form-group--success'); - - if (isValid) { - formGroup.classList.add('form-group--success'); - errorElement.style.display = 'none'; - } else { - formGroup.classList.add('form-group--error'); - errorElement.textContent = errorMessage; - errorElement.style.display = 'block'; - } - } - - function clearValidationState(field) { - const formGroup = document.querySelector(`#${field}`).closest('.form-group'); - if (formGroup) { - formGroup.classList.remove('form-group--error', 'form-group--success'); - const errorElement = document.getElementById(`${field}Error`); - if (errorElement) { - errorElement.style.display = 'none'; - } - } - } - // Send message functionality function sendMessage() { const messageInput = document.getElementById('messageInput'); const nicknameInput = document.getElementById('nickname'); - const nicknameErrorElement = document.getElementById('nicknameError'); - const messageErrorElement = document.getElementById('messageError'); - const message = messageInput?.value?.trim() || ''; const nickname = nicknameInput?.value?.trim() || ''; - // Validate nickname - const nicknameValidation = validateNickname(nickname); - updateValidationUI('nickname', nicknameErrorElement, nicknameValidation.valid, nicknameValidation.message); - - // Validate message - const messageValidation = validateMessage(message); - updateValidationUI('messageInput', messageErrorElement, messageValidation.valid, messageValidation.message); - - // If either validation failed, focus the appropriate field and stop - if (!nicknameValidation.valid) { - nicknameInput.focus(); + if (!nickname) { + if (nicknameInput) { + nicknameInput.style.borderColor = 'var(--dodgers-red)'; + nicknameInput.focus(); + } + UIControls.showToast('Please enter a nickname'); + setTimeout(() => { + if (nicknameInput) nicknameInput.style.borderColor = '#e0e0e0'; + }, 2000); return; } - if (!messageValidation.valid) { - messageInput.focus(); - return; + if (!message) { + return; // Just return quietly for empty message } - // All validation passed - send message via API + // Send message via API API.sendMessage(nickname, message) .then(data => { if (data.success) { - // Clear the form and show success if (messageInput) messageInput.value = ''; AppState.nickname = nickname; - - // Show success confirmation - updateValidationUI('nickname', nicknameErrorElement, true); - updateValidationUI('messageInput', messageErrorElement, true); - - UIControls.showToast('Message sent successfully!'); - - // Clear success state after a delay - setTimeout(() => { - clearValidationState('nickname'); - clearValidationState('messageInput'); - }, 3000); - - AppLogger.log('Message sent successfully'); + // Message will be added automatically by polling } else if (data.error) { - // Show server error - updateValidationUI('messageInput', messageErrorElement, false, 'Failed to send message: ' + data.error); + UIControls.showToast(data.error); UIControls.updateConnectionStatus(false); } }) .catch(error => { AppLogger.error('Error sending message:', error); - updateValidationUI('messageInput', messageErrorElement, false, 'Failed to send message. Please try again.'); + UIControls.showToast('Failed to send message'); UIControls.updateConnectionStatus(false); }); } diff --git a/index.php b/index.php index df0c5f8..20f2a52 100644 --- a/index.php +++ b/index.php @@ -461,11 +461,8 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {
Your ID: Loading...
-
-
- -
- +
+
@@ -484,12 +481,9 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['action'])) {

Send Message

-
-
- - -
- +
+ +
diff --git a/static/css/components.css b/static/css/components.css index ea1c1d0..9056ff8 100644 --- a/static/css/components.css +++ b/static/css/components.css @@ -123,11 +123,10 @@ background: rgba(var(--color-primary-rgb), 0.1); } -/* Focus states for accessibility - WCAG AA compliant */ +/* Focus states for accessibility */ .btn:focus-visible { - outline: 3px solid var(--color-focus); + outline: 2px solid var(--color-focus); outline-offset: 2px; - box-shadow: 0 0 0 1px var(--color-surface), 0 0 0 4px var(--dodgers-blue-100); } /* Loading state */ @@ -226,68 +225,8 @@ } .card:focus-visible { - outline: 3px solid var(--color-focus); + outline: 2px solid var(--color-focus); outline-offset: 2px; - box-shadow: 0 0 0 2px var(--dodgers-blue-100); -} - -/* Form focus states - WCAG AA compliant */ -.form-control:focus, -.form-input:focus, -.form-textarea:focus, -.form-select:focus { - outline: 3px solid var(--color-focus); - outline-offset: 2px; - box-shadow: 0 0 0 2px var(--dodgers-blue-100); - border-color: var(--color-focus); -} - -.chat__input input:focus { - outline: 3px solid var(--color-focus); - outline-offset: 2px; - box-shadow: 0 0 0 2px var(--dodgers-blue-100); -} - -/* Link focus states */ -a:focus-visible { - outline: 3px solid var(--color-focus); - outline-offset: 2px; - text-decoration: underline; -} - -/* Dashboard and control focus states */ -.dashboard__toggle-btn:focus-visible { - outline: 3px solid var(--dodgers-white); - outline-offset: 2px; - background: rgba(255,255,255,0.3); -} - -.video-player__quality-selector:focus-visible, -.video-player__toggle-chat-btn:focus-visible, -.stream-stats__refresh-btn:focus-visible { - outline: 3px solid var(--dodgers-white); - outline-offset: 2px; - background: rgba(255,255,255,0.3); -} - -/* Chat admin controls focus */ -.chat__admin-btn:focus-visible { - outline: 3px solid var(--dodgers-gold); - outline-offset: 1px; -} - -/* Mobile navigation focus */ -.mobile-nav-btn:focus-visible { - outline: 3px solid var(--dodgers-white); - outline-offset: 2px; - background: rgba(255,255,255,0.15); -} - -/* FAB focus */ -.fab:focus-visible { - outline: 3px solid var(--dodgers-white); - outline-offset: 2px; - box-shadow: 0 0 0 1px var(--dodgers-blue-400), var(--elevation-5); } /* Card variants */ @@ -1815,78 +1754,6 @@ a:focus-visible { .chat__input button:active { transform: translateY(0); - box-shadow: var(--elevation-1); -} - -/* Active states for links and form elements */ -a:active { - transform: scale(0.98); -} - -/* Form elements pressed states */ -.form-control:active { - transform: translateY(0); -} - -.form-input:active, -.form-textarea:active, -.form-select:active { - transform: translateY(0); - box-shadow: 0 0 0 2px var(--dodgers-blue-200); -} - -/* Dashboard controls active states */ -.dashboard__toggle-btn:active { - transform: translateY(0); - background: rgba(255,255,255,0.4); -} - -.stats-card:active { - transform: translateY(-1px); - box-shadow: var(--elevation-2); -} - -/* Video controls pressed states */ -.video-player__quality-selector:active, -.video-player__toggle-chat-btn:active, -.stream-stats__refresh-btn:active { - transform: translateY(0); - background: rgba(255,255,255,0.3); -} - -/* Chat admin controls active states */ -.chat__admin-btn:active { - transform: translateY(0); - background: rgba(255,215,0,0.6); -} - -/* Message actions pressed states */ -.delete-btn:active, .ban-btn:active { - transform: scale(0.95); -} - -/* Mobile navigation active states */ -.mobile-nav-btn:active { - transform: scale(0.95) translateY(0); - background: rgba(255,255,255,0.15); -} - -/* FAB active states */ -.fab:active, .fab.secondary:active { - transform: scale(1.05); - box-shadow: var(--elevation-3); -} - -/* Toast notifications active states */ -.toast:active { - transform: translateX(-50%) scale(0.95); -} - -/* Activity and user item active states */ -.activity-item:active, -.user-item:active { - transform: scale(0.98); - background: var(--color-surface-variant); } .chat__empty-state { diff --git a/static/css/variables.css b/static/css/variables.css index 7ff7468..1f9d341 100644 --- a/static/css/variables.css +++ b/static/css/variables.css @@ -109,7 +109,6 @@ --hover-overlay: rgba(255, 255, 255, 0.1); --active-overlay: rgba(255, 255, 255, 0.2); --focus-ring: rgba(0, 90, 156, 0.5); - --color-focus: var(--dodgers-blue-400); /* WCAG AA compliant focus color */ /* Borders & Dividers */ --border-color: #1a2332;