Мобилка
This commit is contained in:
@@ -1574,14 +1574,15 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
animate={{ scale: 1, opacity: 1, y: 0 }}
|
||||
exit={{ scale: 0.9, opacity: 0, y: 20 }}
|
||||
transition={{ type: 'spring', damping: 25, stiffness: 300 }}
|
||||
className={`relative w-full mx-4 rounded-[2.5rem] glass-strong shadow-2xl shadow-black/50 overflow-hidden border border-white/5 ${showVideoArea ? 'max-w-5xl' : 'max-w-md'
|
||||
}`}
|
||||
className={`relative w-full h-full sm:h-auto sm:mx-4 sm:rounded-[2.5rem] glass-strong shadow-2xl shadow-black/50 overflow-hidden border border-white/5 flex flex-col ${
|
||||
showVideoArea ? 'sm:max-w-5xl' : 'sm:max-w-md'
|
||||
}`}
|
||||
>
|
||||
{/* Mic selector popup */}
|
||||
{showMicMenu && microphones.length > 0 && (
|
||||
<>
|
||||
<div className="fixed inset-0 z-[200]" onClick={() => setShowMicMenu(false)} />
|
||||
<div className="fixed z-[201] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[320px] rounded-xl bg-zinc-800/95 backdrop-blur-md border border-zinc-600 shadow-2xl py-2">
|
||||
<div className="fixed z-[201] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90vw] sm:w-[320px] rounded-xl bg-zinc-800/95 backdrop-blur-md border border-zinc-600 shadow-2xl py-2">
|
||||
<div className="px-3 py-1.5 text-xs text-zinc-400 uppercase tracking-wider border-b border-zinc-700 mb-1">{t('selectMicrophone')}</div>
|
||||
{microphones.map((mic, i) => (
|
||||
<button
|
||||
@@ -1603,7 +1604,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
<>
|
||||
{/* Backdrop to close on click outside */}
|
||||
<div className="fixed inset-0 z-[200]" onClick={() => setShowCameraMenu(false)} />
|
||||
<div className="fixed z-[201] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[300px] rounded-xl bg-zinc-800/95 backdrop-blur-md border border-zinc-600 shadow-2xl py-2">
|
||||
<div className="fixed z-[201] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90vw] sm:w-[300px] rounded-xl bg-zinc-800/95 backdrop-blur-md border border-zinc-600 shadow-2xl py-2">
|
||||
<div className="px-3 py-1.5 text-xs text-zinc-400 uppercase tracking-wider border-b border-zinc-700 mb-1">{t('switchCamera')}</div>
|
||||
{cameras.map((cam, i) => (
|
||||
<button
|
||||
@@ -1624,7 +1625,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
{showVolumeSlider && (
|
||||
<>
|
||||
<div className="fixed inset-0 z-[200]" onClick={() => setShowVolumeSlider(false)} />
|
||||
<div className="fixed z-[201] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[250px] rounded-xl bg-zinc-800/95 backdrop-blur-md border border-zinc-600 shadow-2xl p-4" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="fixed z-[201] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 w-[90vw] sm:w-[250px] rounded-xl bg-zinc-800/95 backdrop-blur-md border border-zinc-600 shadow-2xl p-4" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="text-xs text-zinc-400 uppercase tracking-wider mb-3">{t('volume')}</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<Volume2 size={16} className="text-zinc-400 shrink-0" />
|
||||
@@ -1644,7 +1645,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
)}
|
||||
|
||||
{/* === Main Content Area === */}
|
||||
<div className="flex-1 flex flex-col relative overflow-hidden bg-black min-h-[400px]">
|
||||
<div className="flex-1 flex flex-col relative overflow-hidden bg-black min-h-[300px] sm:min-h-[400px]">
|
||||
{showVideoArea ? (
|
||||
<div
|
||||
ref={videoContainerRef}
|
||||
@@ -1664,9 +1665,9 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
<div className="relative mb-6">
|
||||
<div className="absolute inset-0 rounded-full bg-vortex-500/20 animate-ping" />
|
||||
{displayAvatar ? (
|
||||
<img src={displayAvatar} alt="" className="relative w-32 h-32 rounded-full object-cover border-4 border-vortex-500/30" />
|
||||
<img src={displayAvatar} alt="" className="relative w-24 h-24 sm:w-32 sm:h-32 rounded-full object-cover border-4 border-vortex-500/30" />
|
||||
) : (
|
||||
<div className="relative w-32 h-32 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-4xl font-bold border-4 border-vortex-500/30">
|
||||
<div className="relative w-24 h-24 sm:w-32 sm:h-32 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-3xl sm:text-4xl font-bold border-4 border-vortex-500/30">
|
||||
{initials}
|
||||
</div>
|
||||
)}
|
||||
@@ -1709,7 +1710,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
|
||||
{/* Local video PIP (bottom-right) */}
|
||||
{hasLocalVideo && (
|
||||
<div className="absolute bottom-3 right-3 w-48 rounded-xl overflow-hidden border-2 border-white/20 shadow-lg bg-black z-20"
|
||||
<div className="absolute bottom-4 right-4 w-28 sm:w-48 rounded-xl overflow-hidden border-2 border-white/20 shadow-lg bg-black z-20"
|
||||
style={{ aspectRatio: '16 / 9' }}
|
||||
>
|
||||
<video
|
||||
@@ -1772,9 +1773,9 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
)}
|
||||
<div className="relative z-10 p-1.5 rounded-full bg-gradient-to-br from-white/10 to-transparent backdrop-blur-md border border-white/10 shadow-2xl cursor-pointer">
|
||||
{displayAvatar ? (
|
||||
<img src={displayAvatar} alt="" className="w-32 h-32 rounded-full object-cover shadow-inner" />
|
||||
<img src={displayAvatar} alt="" className="w-24 h-24 sm:w-32 sm:h-32 rounded-full object-cover shadow-inner" />
|
||||
) : (
|
||||
<div className="w-32 h-32 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-4xl shadow-inner">
|
||||
<div className="w-24 h-24 sm:w-32 sm:h-32 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-3xl sm:text-4xl shadow-inner">
|
||||
{initials}
|
||||
</div>
|
||||
)}
|
||||
@@ -1806,7 +1807,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
</div>
|
||||
|
||||
{/* === Controls === */}
|
||||
<div className="px-8 pb-10 flex items-center justify-center gap-4 relative z-10 flex-wrap">
|
||||
<div className="px-4 py-6 sm:px-8 sm:pb-10 flex items-center justify-center gap-3 sm:gap-4 relative z-10 flex-wrap">
|
||||
{callState === 'incoming' && (
|
||||
<>
|
||||
<button
|
||||
@@ -1887,7 +1888,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
</button>
|
||||
<button
|
||||
onClick={endCallSafe}
|
||||
className="w-14 h-14 rounded-full bg-red-500 hover:bg-red-600 flex items-center justify-center text-white shadow-xl shadow-red-500/30 transition-all hover:scale-105 ml-2"
|
||||
className="w-12 h-12 sm:w-14 sm:h-14 rounded-full bg-red-500 hover:bg-red-600 flex items-center justify-center text-white shadow-xl shadow-red-500/30 transition-all hover:scale-105"
|
||||
>
|
||||
<PhoneOff size={22} />
|
||||
</button>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
Pin,
|
||||
Forward,
|
||||
Bookmark,
|
||||
ArrowLeft,
|
||||
} from 'lucide-react';
|
||||
import { useChatStore } from '../stores/chatStore';
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
@@ -421,19 +422,17 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
<div
|
||||
ref={chatViewRef}
|
||||
onMouseMove={handleMouseMove}
|
||||
className={`flex-1 flex flex-col h-full rounded-3xl overflow-hidden shadow-[0_0_120px_-20px_rgba(0,0,0,0.5)] border border-border/50 relative z-0 chat-theme-${chatTheme} transition-colors duration-500`}
|
||||
className={`flex-1 flex flex-col h-full sm:rounded-[2rem] overflow-hidden shadow-[0_0_120px_-20px_rgba(0,0,0,0.5)] border-x sm:border border-border/50 relative z-0 chat-theme-${chatTheme} transition-colors duration-500`}
|
||||
>
|
||||
{/* Шапка чата */}
|
||||
{selectionMode ? (
|
||||
<div className="h-[76px] flex items-center justify-between px-6 border-b border-border/40 bg-surface-secondary/80 backdrop-blur-xl z-20 flex-shrink-0 animate-in slide-in-from-top-2">
|
||||
<div className="flex items-center gap-4 text-white">
|
||||
<div className="h-[76px] flex items-center justify-between px-4 sm:px-6 border-b border-border/40 bg-surface-secondary/80 backdrop-blur-xl z-20 flex-shrink-0 animate-in slide-in-from-top-2">
|
||||
<div className="flex items-center gap-2 sm:gap-4 text-white">
|
||||
<button onClick={() => { setSelectionMode(false); setSelectedMessages(new Set()); }} className="p-2 -ml-2 rounded-full hover:bg-white/10 transition">
|
||||
<X size={20} className="text-zinc-300" />
|
||||
</button>
|
||||
<span className="font-medium text-[15px]">{selectedMessages.size} {t('selected') || 'выбрано'}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
{/* Кнопка удаления с выпадающим меню */}
|
||||
<div className="relative" ref={deleteMenuRef}>
|
||||
<button
|
||||
disabled={selectedMessages.size === 0}
|
||||
@@ -482,52 +481,59 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-[76px] flex items-center justify-between px-6 border-b border-border/40 bg-surface-secondary/80 backdrop-blur-xl z-20 flex-shrink-0">
|
||||
<button
|
||||
className="flex items-center gap-3 min-w-0 flex-1 group transition-all"
|
||||
onClick={() => {
|
||||
if (chat.type === 'personal' && otherMember) {
|
||||
setProfileUserId(otherMember.user.id);
|
||||
} else if (chat.type === 'group') {
|
||||
setShowGroupSettings(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="relative flex-shrink-0 transform transition-transform duration-300 group-hover:scale-105">
|
||||
{isFavorites ? (
|
||||
<div className="w-11 h-11 rounded-full bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center shadow-lg ring-2 ring-transparent group-hover:ring-accent/30 transition-all duration-300">
|
||||
<Bookmark size={20} className="text-white" />
|
||||
</div>
|
||||
) : (
|
||||
<Avatar
|
||||
src={chatAvatar}
|
||||
name={chatName}
|
||||
size="md"
|
||||
online={isOnline ? true : undefined}
|
||||
className="ring-2 ring-transparent group-hover:ring-accent/30 transition-all duration-300 rounded-full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0 text-left">
|
||||
<h3 className="text-base font-semibold text-white truncate drop-shadow-sm group-hover:text-accent/90 transition-colors">{chatName}</h3>
|
||||
<p className="text-xs text-zinc-400 truncate">
|
||||
{isFavorites
|
||||
? t('favoritesDescription')
|
||||
: typingInChat.length > 0
|
||||
? <span className="text-accent font-medium">{t('typing')}</span>
|
||||
: isOnline
|
||||
? <span className="text-emerald-400">{t('online')}</span>
|
||||
: chat.type === 'personal' && otherMember?.user.lastSeen
|
||||
? `${t('lastSeenAt')} ${formatLastSeen(otherMember.user.lastSeen, lang)}`
|
||||
: chat.type === 'group'
|
||||
? `${chat.members.length} ${t('members')}`
|
||||
: ''}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
<div className="h-[76px] flex items-center justify-between px-3 sm:px-6 border-b border-border/40 bg-surface-secondary/80 backdrop-blur-xl z-20 flex-shrink-0 gap-2">
|
||||
<div className="flex items-center gap-2 min-w-0 flex-1">
|
||||
<button
|
||||
onClick={() => setActiveChat(null)}
|
||||
className="sm:hidden p-2 -ml-1 rounded-xl hover:bg-surface-hover text-zinc-400 transition-colors"
|
||||
>
|
||||
<ArrowLeft size={22} />
|
||||
</button>
|
||||
<button
|
||||
className="flex items-center gap-3 min-w-0 flex-1 group transition-all"
|
||||
onClick={() => {
|
||||
if (chat.type === 'personal' && otherMember) {
|
||||
setProfileUserId(otherMember.user.id);
|
||||
} else if (chat.type === 'group') {
|
||||
setShowGroupSettings(true);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="relative flex-shrink-0 transform transition-transform duration-300 group-hover:scale-105">
|
||||
{isFavorites ? (
|
||||
<div className="w-11 h-11 rounded-full bg-gradient-to-br from-amber-400 to-orange-500 flex items-center justify-center shadow-lg ring-2 ring-transparent group-hover:ring-accent/30 transition-all duration-300">
|
||||
<Bookmark size={20} className="text-white" />
|
||||
</div>
|
||||
) : (
|
||||
<Avatar
|
||||
src={chatAvatar}
|
||||
name={chatName}
|
||||
size="md"
|
||||
online={isOnline ? true : undefined}
|
||||
className="ring-2 ring-transparent group-hover:ring-accent/30 transition-all duration-300 rounded-full"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="min-w-0 text-left">
|
||||
<h3 className="text-base font-semibold text-white truncate drop-shadow-sm group-hover:text-accent/90 transition-colors">{chatName}</h3>
|
||||
<p className="text-xs text-zinc-400 truncate">
|
||||
{isFavorites
|
||||
? t('favoritesDescription')
|
||||
: typingInChat.length > 0
|
||||
? <span className="text-accent font-medium">{t('typing')}</span>
|
||||
: isOnline
|
||||
? <span className="text-emerald-400">{t('online')}</span>
|
||||
: chat.type === 'personal' && otherMember?.user.lastSeen
|
||||
? `${t('lastSeenAt')} ${formatLastSeen(otherMember.user.lastSeen, lang)}`
|
||||
: chat.type === 'group'
|
||||
? `${chat.members.length} ${t('members')}`
|
||||
: ''}
|
||||
</p>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-1.5 ml-4">
|
||||
{/* Поиск */}
|
||||
<AnimatePresence>
|
||||
{showSearch && (
|
||||
<motion.div
|
||||
@@ -590,7 +596,6 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Меню */}
|
||||
<div className="relative" ref={topMenuRef}>
|
||||
<button
|
||||
onClick={() => setShowTopMenu(!showTopMenu)}
|
||||
|
||||
@@ -68,7 +68,7 @@ export default function Sidebar() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-[340px] h-full flex flex-col bg-surface-secondary rounded-3xl overflow-hidden border border-border/50 shadow-2xl relative z-10">
|
||||
<div className="w-full h-full flex flex-col bg-surface-secondary sm:rounded-[2rem] overflow-hidden border-x sm:border border-border/50 shadow-2xl relative z-10">
|
||||
{/* Шапка */}
|
||||
<div className="h-[76px] px-4 flex items-center gap-3 border-b border-border/40 bg-surface-secondary flex-shrink-0">
|
||||
<button
|
||||
@@ -121,10 +121,10 @@ export default function Sidebar() {
|
||||
onClick={() => setShowCreateStory(true)}
|
||||
className="flex flex-col items-center gap-1 flex-shrink-0 group"
|
||||
>
|
||||
<div className="w-14 h-14 rounded-full border-2 border-dashed border-zinc-600 flex items-center justify-center group-hover:border-vortex-400 transition-colors">
|
||||
<Plus size={20} className="text-zinc-400 group-hover:text-vortex-400 transition-colors" />
|
||||
<div className="w-12 h-12 sm:w-14 sm:h-14 rounded-full border-2 border-dashed border-zinc-600 flex items-center justify-center group-hover:border-vortex-400 transition-colors">
|
||||
<Plus size={18} className="text-zinc-400 group-hover:text-vortex-400 transition-colors" />
|
||||
</div>
|
||||
<span className="text-[10px] text-zinc-500 truncate w-14 text-center">{t('newStory')}</span>
|
||||
<span className="text-[10px] text-zinc-500 truncate w-12 sm:w-14 text-center">{t('newStory')}</span>
|
||||
</button>
|
||||
|
||||
{storyGroups.map((group, idx) => {
|
||||
|
||||
@@ -257,15 +257,21 @@ export default function ChatPage() {
|
||||
setGroupCallOpen(false);
|
||||
};
|
||||
|
||||
const activeChat = useChatStore((state) => state.activeChat);
|
||||
|
||||
return (
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="h-full flex bg-surface p-3 gap-3 overflow-hidden"
|
||||
className="h-full flex bg-surface sm:p-3 sm:gap-3 overflow-hidden"
|
||||
>
|
||||
<Sidebar />
|
||||
<ChatView onStartCall={handleStartCall} onStartGroupCall={handleStartGroupCall} />
|
||||
<div className={`${activeChat ? 'hidden sm:block' : 'block'} w-full sm:w-[340px] flex-shrink-0`}>
|
||||
<Sidebar />
|
||||
</div>
|
||||
<div className={`${activeChat ? 'block' : 'hidden sm:block'} flex-1 h-full min-w-0`}>
|
||||
<ChatView onStartCall={handleStartCall} onStartGroupCall={handleStartGroupCall} />
|
||||
</div>
|
||||
<CallModal
|
||||
key={callSessionId}
|
||||
isOpen={callOpen}
|
||||
|
||||
Reference in New Issue
Block a user