diff --git a/app/dashboard/admin/ban/page.tsx b/app/dashboard/admin/ban/page.tsx index 220adb5..e6417e3 100644 --- a/app/dashboard/admin/ban/page.tsx +++ b/app/dashboard/admin/ban/page.tsx @@ -1,3 +1,5 @@ +"use client"; + import React from "react"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; diff --git a/app/dashboard/chats/layout.tsx b/app/dashboard/chats/layout.tsx index e6aae9c..51463be 100644 --- a/app/dashboard/chats/layout.tsx +++ b/app/dashboard/chats/layout.tsx @@ -8,6 +8,8 @@ export const metadata: Metadata = { export const viewport: Viewport = { width: "device-width", initialScale: 1, + maximumScale: 5, + userScalable: true, themeColor: [ { media: "(prefers-color-scheme: dark)", color: "#000000" }, { media: "(prefers-color-scheme: light)", color: "#D53F8C" }, diff --git a/app/globals.css b/app/globals.css index 1dcb0fc..f1c9426 100644 --- a/app/globals.css +++ b/app/globals.css @@ -10,6 +10,89 @@ body { .text-balance { text-wrap: balance; } + + /* Accessibility improvements */ + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; + } + + /* Better focus states for keyboard navigation */ + .focus-visible:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: 2px; + } + + /* Improved touch targets for mobile/Chromebook */ + .touch-target { + min-height: 44px; + min-width: 44px; + } + + /* Better contrast for Chromebook displays */ + @media (prefers-contrast: high) { + .border-input { + border-color: hsl(var(--foreground)); + } + } + + /* Chromebook and touch device optimizations */ + @media (pointer: coarse) { + .touch-target { + min-height: 48px; + min-width: 48px; + } + + /* Larger touch targets for interactive elements */ + button, input, textarea, [role="button"] { + min-height: 44px; + } + } + + /* Better focus indicators for keyboard navigation */ + .focus-visible:focus-visible { + outline: 2px solid hsl(var(--ring)); + outline-offset: 2px; + box-shadow: 0 0 0 2px hsl(var(--ring) / 0.2); + } + + /* Improved scrolling for touch devices */ + .overflow-y-auto { + -webkit-overflow-scrolling: touch; + scroll-behavior: smooth; + } + + /* Enhanced contrast for better visibility */ + .text-muted-foreground { + color: hsl(var(--muted-foreground) / 0.8); + } + + /* Better button contrast */ + button:not(:disabled):hover { + filter: brightness(1.05); + } + + /* Improved focus visibility */ + input:focus, textarea:focus, button:focus { + outline: 2px solid hsl(var(--ring)); + outline-offset: 2px; + } + + /* Better message bubble contrast */ + .bg-primary { + background-color: hsl(var(--primary) / 0.9); + } + + .bg-muted { + background-color: hsl(var(--muted) / 0.8); + } } @layer base { diff --git a/app/layout.tsx b/app/layout.tsx index 09d6882..e0ddcd9 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -55,7 +55,8 @@ export const metadata: Metadata = { export const viewport: Viewport = { width: "device-width", initialScale: 1, - maximumScale: 1, + maximumScale: 5, + userScalable: true, themeColor: [ { media: "(prefers-color-scheme: dark)", color: "#000000" }, { media: "(prefers-color-scheme: light)", color: "#D53F8C" }, diff --git a/components/dashboard/ChatDetail.tsx b/components/dashboard/ChatDetail.tsx index cf99e6c..3637a02 100644 --- a/components/dashboard/ChatDetail.tsx +++ b/components/dashboard/ChatDetail.tsx @@ -14,6 +14,7 @@ import { ArrowLeft, Send, RefreshCw, File, FileText, Image as ImageIcon, Downloa import { getCookie, clientFetch } from "@/lib/api"; import { ImageViewerModal } from "@/components/modals/image-viewer-modal"; import BuyerOrderInfo from "./BuyerOrderInfo"; +import { useIsTouchDevice } from "@/hooks/use-mobile"; interface Message { _id: string; @@ -98,6 +99,7 @@ export default function ChatDetail({ chatId }: { chatId: string }) { const [selectedMessageIndex, setSelectedMessageIndex] = useState(null); const [selectedAttachmentIndex, setSelectedAttachmentIndex] = useState(null); const seenMessageIdsRef = useRef>(new Set()); + const isTouchDevice = useIsTouchDevice(); // Scroll to bottom utility functions const scrollToBottom = () => { @@ -358,6 +360,16 @@ export default function ChatDetail({ chatId }: { chatId: string }) { if (message.trim()) { sendMessage(message); } + } else if (e.key === 'Escape') { + // Clear the input on Escape + setMessage(''); + } else if (e.key === 'ArrowUp' && message === '') { + // Load previous message on Arrow Up when input is empty + e.preventDefault(); + const lastVendorMessage = [...messages].reverse().find(msg => msg.sender === 'vendor'); + if (lastVendorMessage) { + setMessage(lastVendorMessage.content); + } } }; @@ -548,17 +560,37 @@ export default function ChatDetail({ chatId }: { chatId: string }) { return (
-
+
-
-

+

Chat with Customer {chat.buyerId}

{chat.telegramUsername && ( - + @{chat.telegramUsername} )} @@ -568,7 +600,13 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
-
+
{chat.messages.length === 0 ? (

No messages yet. Send one to start the conversation.

@@ -581,6 +619,8 @@ export default function ChatDetail({ chatId }: { chatId: string }) { "flex mb-4", msg.sender === "vendor" ? "justify-end" : "justify-start" )} + role="article" + aria-label={`Message from ${msg.sender === "vendor" ? "you" : "customer"}`} >
{msg.sender === "buyer" && ( - + {chat.buyerId.slice(0, 2).toUpperCase()} )} - + {formatDistanceToNow(new Date(msg.createdAt), { addSuffix: true })}
-

{msg.content}

+

{msg.content}

{/* Show attachments if any */} {msg.attachments && msg.attachments.length > 0 && (
@@ -618,10 +658,19 @@ export default function ChatDetail({ chatId }: { chatId: string }) { key={`attachment-${attachmentIndex}`} className="rounded-md overflow-hidden bg-background/20 p-1" onClick={() => handleImageClick(attachment, messageIndex, attachmentIndex)} + role="button" + tabIndex={0} + aria-label={`View image: ${fileName}`} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + e.preventDefault(); + handleImageClick(attachment, messageIndex, attachmentIndex); + } + }} > -
+
-
+
{fileName}
@@ -661,8 +711,9 @@ export default function ChatDetail({ chatId }: { chatId: string }) { target="_blank" rel="noopener noreferrer" className="ml-2 p-1 rounded-sm hover:bg-background/50" + aria-label={`Download file: ${fileName}`} > - +
); @@ -676,21 +727,44 @@ export default function ChatDetail({ chatId }: { chatId: string }) {
-
+
setMessage(e.target.value)} placeholder="Type your message..." disabled={sending} - className="flex-1" + className={cn( + "flex-1 text-base transition-all duration-200", + isTouchDevice ? "min-h-[48px] text-lg" : "min-h-[44px]" + )} onKeyDown={handleKeyDown} autoFocus + aria-label="Message input" + aria-describedby="message-help" + role="textbox" + autoComplete="off" + spellCheck="true" + maxLength={2000} /> -
+
+ Press Enter to send message, Shift+Enter for new line, Escape to clear, Arrow Up to load previous message. Maximum 2000 characters. +
{/* Update the image viewer modal */} diff --git a/components/ui/input.tsx b/components/ui/input.tsx index 19dc1da..8be3061 100644 --- a/components/ui/input.tsx +++ b/components/ui/input.tsx @@ -8,7 +8,7 @@ const Input = React.forwardRef>( (undefined) + + React.useEffect(() => { + const checkTouch = () => { + const hasTouch = 'ontouchstart' in window || navigator.maxTouchPoints > 0 + setIsTouch(hasTouch) + } + + checkTouch() + + // Listen for changes in touch capability + const mediaQuery = window.matchMedia('(pointer: coarse)') + const handleChange = () => checkTouch() + + mediaQuery.addEventListener('change', handleChange) + return () => mediaQuery.removeEventListener('change', handleChange) + }, []) + + return !!isTouch +} diff --git a/public/git-info.json b/public/git-info.json index ddb90fb..253b8d1 100644 --- a/public/git-info.json +++ b/public/git-info.json @@ -1,4 +1,4 @@ { - "commitHash": "03a2e37", - "buildTime": "2025-10-16T20:09:52.215Z" + "commitHash": "bfc6001", + "buildTime": "2025-10-22T16:52:33.065Z" } \ No newline at end of file