From cacfb17ec63782bceb2045717c2d47eb9d8d433d Mon Sep 17 00:00:00 2001 From: Mahyar Khanbolooki Date: Thu, 21 Aug 2025 22:00:14 +0330 Subject: [PATCH] fix: chat page styles for dark theme --- src/app/[name]/(Main)/chat/[id]/page.tsx | 255 +++++------ src/app/[name]/(Main)/chat/nearby/page.tsx | 280 +++++++------ src/app/[name]/(Main)/chat/page.tsx | 464 +++++++++++---------- 3 files changed, 548 insertions(+), 451 deletions(-) diff --git a/src/app/[name]/(Main)/chat/[id]/page.tsx b/src/app/[name]/(Main)/chat/[id]/page.tsx index c3d821b..42975fe 100644 --- a/src/app/[name]/(Main)/chat/[id]/page.tsx +++ b/src/app/[name]/(Main)/chat/[id]/page.tsx @@ -1,137 +1,160 @@ -'use client'; +'use client' -import { ef } from '@/lib/helpers/utfNumbers'; -import { EmojiHappy, Microphone2, Paperclip2 } from 'iconsax-react'; +import { ef } from '@/lib/helpers/utfNumbers' +import { EmojiHappy, Microphone2, Paperclip2 } from 'iconsax-react' import React, { useMemo, useState } from 'react' -import { useTranslation } from 'react-i18next'; +import { useTranslation } from 'react-i18next' type Props = object type MessageModel = { - type: "sender" | "receiver", - senderName: string, - content: string, - date: string, - time: string, + type: 'sender' | 'receiver' + senderName: string + content: string + date: string + time: string } -function ChatIndex({ }: Props) { +function ChatIndex ({}: Props) { + const { t } = useTranslation('chat') - const { t } = useTranslation('chat'); - - const [messages, setMessages] = useState>([ - { - type: "sender", - senderName: "علی مصلحی", - content: 'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است', - date: '1403/09/30', - time: '10:07' - }, - { - type: 'receiver', - senderName: 'سیما فرهادی', - content: 'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است', - date: '1403/09/30', - time: '10:07' - }, - ]); - - const addMessageOutgoing = (model: MessageModel) => { - setMessages((previous) => { - return [...previous, model] - }) + const [messages, setMessages] = useState>([ + { + type: 'sender', + senderName: 'علی مصلحی', + content: + 'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است', + date: '1403/09/30', + time: '10:07' + }, + { + type: 'receiver', + senderName: 'سیما فرهادی', + content: + 'لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ، و با استفاده از طراحان گرافیک است، چاپگرها و متون بلکه روزنامه و مجله در ستون و سطرآنچنان که لازم است', + date: '1403/09/30', + time: '10:07' } + ]) - const submitMessage = (e: React.FormEvent) => { - e.preventDefault(); - const formData = new FormData(e.currentTarget); - const content = formData.get('textMessage'); - if (!content) return; - const model: MessageModel = { - type: 'sender', - senderName: 'علی مصلحی', - content: String(content), - date: '1403/09/30', - time: '10:07' - } - addMessageOutgoing(model); - }; + const addMessageOutgoing = (model: MessageModel) => { + setMessages(previous => { + return [...previous, model] + }) + } + const submitMessage = (e: React.FormEvent) => { + e.preventDefault() + const formData = new FormData(e.currentTarget) + const content = formData.get('textMessage') + if (!content) return + const model: MessageModel = { + type: 'sender', + senderName: 'علی مصلحی', + content: String(content), + date: '1403/09/30', + time: '10:07' + } + addMessageOutgoing(model) + } - const messagesMemo = useMemo(() => messages, [messages]); + const messagesMemo = useMemo(() => messages, [messages]) - return ( -
-

علی مصلحی

+ return ( +
+

علی مصلحی

-
-
-
    - {messagesMemo.map((v, i) => { - if (v.type === "sender") { - return ( -
  • -
    -

    - {v.content} -

    +
    +
    +
      + {messagesMemo.map((v, i) => { + if (v.type === 'sender') { + return ( +
    • +
      +

      {v.content}

      - - {ef(`${v.time} | ${v.date}`)} - -
      -
    • - ) - } - else { - return ( -
    • -
      -
      - {v.senderName} -
      -

      - {v.content} -

      + + {ef(`${v.time} | ${v.date}`)} + +
      +
    • + ) + } else { + return ( +
    • +
      +
      {v.senderName}
      +

      {v.content}

      - - {ef(`${v.time} | ${v.date}`)} - -
      -
    • - ) - } - })} -
    -
    + + {ef(`${v.time} | ${v.date}`)} + +
    +
  • + ) + } + })} +
+
-
-
- - - - -
-
-
+
+
+ + + + +
- ) + +
+ ) } -export default ChatIndex \ No newline at end of file +export default ChatIndex diff --git a/src/app/[name]/(Main)/chat/nearby/page.tsx b/src/app/[name]/(Main)/chat/nearby/page.tsx index 6b12647..dbc9184 100644 --- a/src/app/[name]/(Main)/chat/nearby/page.tsx +++ b/src/app/[name]/(Main)/chat/nearby/page.tsx @@ -1,140 +1,184 @@ -'use client'; +'use client' -import { motion } from 'framer-motion'; -import Image from 'next/image'; -import Link from 'next/link'; -import React, { useEffect, useRef, useState } from 'react'; +import { motion } from 'framer-motion' +import Image from 'next/image' +import Link from 'next/link' +import React, { useEffect, useRef, useState } from 'react' const users = [ - { id: 0, name: '1 علی مصلحی', table: 12, avatar: '/assets/images/user-avatar.png' }, - { id: 1, name: '2 علی مصلحی', table: 10, avatar: '/assets/images/user-avatar.png' }, - { id: 2, name: '3 علی مصلحی', table: 11, avatar: '/assets/images/user-avatar.png' }, - { id: 3, name: '4 علی مصلحی', table: 5, avatar: '/assets/images/user-avatar.png' }, - { id: 4, name: '5 علی مصلحی', table: 2, avatar: '/assets/images/user-avatar.png' }, - { id: 5, name: '6 علی مصلحی', table: 1, avatar: '/assets/images/user-avatar.png' }, -]; + { + id: 0, + name: '1 علی مصلحی', + table: 12, + avatar: '/assets/images/user-avatar.png' + }, + { + id: 1, + name: '2 علی مصلحی', + table: 10, + avatar: '/assets/images/user-avatar.png' + }, + { + id: 2, + name: '3 علی مصلحی', + table: 11, + avatar: '/assets/images/user-avatar.png' + }, + { + id: 3, + name: '4 علی مصلحی', + table: 5, + avatar: '/assets/images/user-avatar.png' + }, + { + id: 4, + name: '5 علی مصلحی', + table: 2, + avatar: '/assets/images/user-avatar.png' + }, + { + id: 5, + name: '6 علی مصلحی', + table: 1, + avatar: '/assets/images/user-avatar.png' + } +] -function ChatNearby() { - const containerRef = useRef(null); - const [positions, setPositions] = useState<{ top: number; left: number }[]>([]); +function ChatNearby () { + const containerRef = useRef(null) + const [positions, setPositions] = useState<{ top: number; left: number }[]>( + [] + ) - useEffect(() => { - if (containerRef.current) { - const maxW = containerRef.current.offsetWidth - 50; - const maxH = containerRef.current.offsetHeight - 180; - const elementSize = 110; // approximate size of each user element - const padding = 80; // minimum distance between elements + useEffect(() => { + if (containerRef.current) { + const maxW = containerRef.current.offsetWidth - 50 + const maxH = containerRef.current.offsetHeight - 180 + const elementSize = 110 // approximate size of each user element + const padding = 80 // minimum distance between elements - const positions: { top: number; left: number }[] = []; + const positions: { top: number; left: number }[] = [] - const isTooClose = (top: number, left: number) => { - return positions.some(p => { - const dx = p.left - left; - const dy = p.top - top; - const distance = Math.sqrt(dx * dx + dy * dy); - return distance < padding; - }); - }; + const isTooClose = (top: number, left: number) => { + return positions.some(p => { + const dx = p.left - left + const dy = p.top - top + const distance = Math.sqrt(dx * dx + dy * dy) + return distance < padding + }) + } - users.forEach(() => { - let top = 0; - let left = 0; - let tries = 0; + users.forEach(() => { + let top = 0 + let left = 0 + let tries = 0 - do { - top = Math.random() * Math.abs(maxH - elementSize); - left = Math.random() * Math.abs(maxW - elementSize); - tries++; - } while (isTooClose(top, left) && tries < 100); + do { + top = Math.random() * Math.abs(maxH - elementSize) + left = Math.random() * Math.abs(maxW - elementSize) + tries++ + } while (isTooClose(top, left) && tries < 100) - positions.push({ top, left }); - }); + positions.push({ top, left }) + }) - setPositions(positions); - } - }, []); + setPositions(positions) + } + }, []) + return ( +
+
+ - return ( -
s -
- - + - - + - - + - - + - - + - - className='size-[174px] rounded-full fixed top-1/2 left-1/2 -translate-1/2' - style={{ background: 'linear-gradient(180deg, #F3F7FF 0%, #F2F5FC 100%)', willChange: 'transform' }} - > - - - - - -
- {positions.length === users.length && - users.map((v, i) => ( - - user avatar -
{v.name}
-
- میز {v.table} -
- - ))} +
+ {positions.length === users.length && + users.map((v, i) => ( + + user avatar +
{v.name}
+
+ میز {v.table}
-
-
- ); + + ))} + +
+
+ ) } -export default ChatNearby; +export default ChatNearby diff --git a/src/app/[name]/(Main)/chat/page.tsx b/src/app/[name]/(Main)/chat/page.tsx index 52a7e43..fd58810 100644 --- a/src/app/[name]/(Main)/chat/page.tsx +++ b/src/app/[name]/(Main)/chat/page.tsx @@ -1,235 +1,265 @@ -'use client'; +'use client' -import SearchBox from '@/components/input/SearchBox'; -import { ScrollArea } from '@/components/ui/scrollarea'; -import { Radar2, Trash } from 'iconsax-react'; -import { Check, CheckCheck } from 'lucide-react'; -import Image from 'next/image'; -import Link from 'next/link'; +import SearchBox from '@/components/input/SearchBox' +import { ScrollArea } from '@/components/ui/scrollarea' +import { Radar2, Trash } from 'iconsax-react' +import { Check, CheckCheck } from 'lucide-react' +import Image from 'next/image' +import Link from 'next/link' import React, { useCallback, useState } from 'react' -import { useTranslation } from 'react-i18next'; -import { LongPressCallbackMeta, LongPressReactEvents, useLongPress } from 'use-long-press' -import { useRouter } from 'next/navigation'; -import useToggle from '@/hooks/helpers/useToggle'; -import Prompt from '@/components/utils/Prompt'; +import { useTranslation } from 'react-i18next' +import { + LongPressCallbackMeta, + LongPressReactEvents, + useLongPress +} from 'use-long-press' +import { useRouter } from 'next/navigation' +import useToggle from '@/hooks/helpers/useToggle' +import Prompt from '@/components/utils/Prompt' type Props = object type ChatEntryModel = { - id: string, - username: string, - avatarUrl: string, - update?: { - time: string, - content: string, - count?: number, - seen?: boolean - } + id: string + username: string + avatarUrl: string + update?: { + time: string + content: string + count?: number + seen?: boolean + } } -function ChatIndex({ }: Props) { - const { t } = useTranslation('chat'); - const { t: tDeleteModal } = useTranslation('common', { - keyPrefix: 'DeleteChatModal' - }); - const [search, setSearch] = useState(''); - const [selectedChats, setSelectedChats] = useState>([]); - const router = useRouter(); - const { state: deleteModal, toggle: toggleDeleteModal } = useToggle(); +function ChatIndex ({}: Props) { + const { t } = useTranslation('chat') + const { t: tDeleteModal } = useTranslation('common', { + keyPrefix: 'DeleteChatModal' + }) + const [search, setSearch] = useState('') + const [selectedChats, setSelectedChats] = useState>([]) + const router = useRouter() + const { state: deleteModal, toggle: toggleDeleteModal } = useToggle() - const [chatsList, setChatList] = useState>([ - { - id: '0', - username: 'علی مصلحی', - avatarUrl: '/assets/images/user-avatar.png', - update: { - time: '12:03', - content: 'چه خبر', - count: 2, - } - }, - { - id: '1', - username: 'علی مصلحی', - avatarUrl: '/assets/images/user-avatar.png', - update: { - time: '12:03', - content: 'چه خبر', - count: 2, - } - }, - { - id: '2', - username: 'علی مصلحی', - avatarUrl: '/assets/images/user-avatar.png', - update: { - time: '12:03', - content: 'چه خبر', - seen: true - } - }, - { - id: '3', - username: 'علی مصلحی', - avatarUrl: '/assets/images/user-avatar.png', - update: { - time: '12:03', - content: 'چه خبر', - seen: true - } - }, - { - id: '4', - username: 'علی مصلحی', - avatarUrl: '/assets/images/user-avatar.png', - update: { - time: '12:03', - content: 'چه خبر', - seen: true - } - }, - { - id: '5', - username: 'علی مصلحی', - avatarUrl: '/assets/images/user-avatar.png', - update: { - time: '12:03', - content: 'چه خبر', - seen: false - } - }, - ]); - - const toggleDeleteModalVisiblity = (e?: React.MouseEvent) => { - e?.preventDefault(); - e?.stopPropagation(); - if (deleteModal) { - setSelectedChats([]); - } - toggleDeleteModal() - }; - - const deleteSelectedChats = () => { - if (setSelectedChats.length <= 0) return; - setChatList((prev) => { - return prev.filter(x => !selectedChats.includes(x.id)); - }) - toggleDeleteModalVisiblity(); + const [chatsList, setChatList] = useState>([ + { + id: '0', + username: 'علی مصلحی', + avatarUrl: '/assets/images/user-avatar.png', + update: { + time: '12:03', + content: 'چه خبر', + count: 2 + } + }, + { + id: '1', + username: 'علی مصلحی', + avatarUrl: '/assets/images/user-avatar.png', + update: { + time: '12:03', + content: 'چه خبر', + count: 2 + } + }, + { + id: '2', + username: 'علی مصلحی', + avatarUrl: '/assets/images/user-avatar.png', + update: { + time: '12:03', + content: 'چه خبر', + seen: true + } + }, + { + id: '3', + username: 'علی مصلحی', + avatarUrl: '/assets/images/user-avatar.png', + update: { + time: '12:03', + content: 'چه خبر', + seen: true + } + }, + { + id: '4', + username: 'علی مصلحی', + avatarUrl: '/assets/images/user-avatar.png', + update: { + time: '12:03', + content: 'چه خبر', + seen: true + } + }, + { + id: '5', + username: 'علی مصلحی', + avatarUrl: '/assets/images/user-avatar.png', + update: { + time: '12:03', + content: 'چه خبر', + seen: false + } } + ]) - const longPressCallback = useCallback((e: LongPressReactEvents, ctx: LongPressCallbackMeta) => { - e.preventDefault(); - if (!ctx?.context) return; - const id = String(ctx.context); - setSelectedChats((prev) => { - if (prev.includes(id)) return prev.filter(x => x !== id); - return [...prev, id] + const toggleDeleteModalVisiblity = (e?: React.MouseEvent) => { + e?.preventDefault() + e?.stopPropagation() + if (deleteModal) { + setSelectedChats([]) + } + toggleDeleteModal() + } + + const deleteSelectedChats = () => { + if (setSelectedChats.length <= 0) return + setChatList(prev => { + return prev.filter(x => !selectedChats.includes(x.id)) + }) + toggleDeleteModalVisiblity() + } + + const longPressCallback = useCallback( + (e: LongPressReactEvents, ctx: LongPressCallbackMeta) => { + e.preventDefault() + if (!ctx?.context) return + const id = String(ctx.context) + setSelectedChats(prev => { + if (prev.includes(id)) return prev.filter(x => x !== id) + return [...prev, id] + }) + }, + [] + ) + + const longPressCancelledCallback = useCallback( + (id: string, reason?: string) => { + if (reason && reason !== 'cancelled-by-release') return + if (selectedChats.length > 0) { + setSelectedChats(prev => { + if (prev.includes(id)) return prev.filter(x => x !== id) + return [...prev, id] }) - }, []) + } else { + router.push(`chat/${id}`) + } + }, + [router, selectedChats.length] + ) - const longPressCancelledCallback = useCallback((id: string, reason?: string) => { - if (reason && reason !== 'cancelled-by-release') return; - if (selectedChats.length > 0) { - setSelectedChats((prev) => { - if (prev.includes(id)) return prev.filter(x => x !== id); - return [...prev, id] - }) - } else { - router.push(`chat/${id}`) - } - }, [router, selectedChats.length]) + const longPress = useLongPress(longPressCallback, { + onCancel: (event, ctx) => + longPressCancelledCallback(String(ctx.context), String(ctx.reason)), + threshold: 500, // In milliseconds + captureEvent: true, // Event won't get cleared after React finish processing it + cancelOnMovement: 100, // Square side size (in pixels) inside which movement won't cancel long press + cancelOutsideElement: true // Cancel long press when moved mouse / pointer outside element while pressing + }) - const longPress = useLongPress(longPressCallback, { - onCancel: (event, ctx) => longPressCancelledCallback(String(ctx.context), String(ctx.reason)), - threshold: 500, // In milliseconds - captureEvent: true, // Event won't get cleared after React finish processing it - cancelOnMovement: 100, // Square side size (in pixels) inside which movement won't cancel long press - cancelOutsideElement: true, // Cancel long press when moved mouse / pointer outside element while pressing - }); + return ( +
+
+ setSearch(e.target.value)} + /> - return ( -
-
- setSearch(e.target.value)} /> - - {selectedChats.length > 0 ? - - - - : - - - - } -
- -
- -
    - {chatsList.map((chat, i) => { - return ( -
  • -
    - { - selectedChats.includes(chat.id) ? -
    - -
    - : - {`${chat.username}'s - } - -
    - -
    -
    {chat.username}
    -

    {chat.update?.content}

    -
    - -
    - {chat.update?.time} - {chat.update?.count !== undefined && chat.update?.count > 0 ? - - {chat.update?.count} - - : - chat.update?.seen !== undefined && - chat.update?.seen ? - : - - } - -
    -
  • - ) - })} -
-
-
- - 0 ? ( + + -
- ) + + ) : ( + + + + )} +
+ +
+ +
    + {chatsList.map((chat, i) => { + return ( +
  • +
    + {selectedChats.includes(chat.id) ? ( +
    + +
    + ) : ( + {`${chat.username}'s + )} +
    + +
    +
    {chat.username}
    +

    {chat.update?.content}

    +
    + +
    + + {chat.update?.time} + + {chat.update?.count !== undefined && + chat.update?.count > 0 ? ( + + + {chat.update?.count} + + + ) : chat.update?.seen !== undefined && chat.update?.seen ? ( + + ) : ( + + )} +
    +
  • + ) + })} +
+
+
+ + +
+ ) } -export default ChatIndex \ No newline at end of file +export default ChatIndex