Очистка мусора
This commit is contained in:
+1
-1
@@ -2,7 +2,7 @@
|
||||
<html lang="ru" class="dark">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>SelfHost Messenger</title>
|
||||
<title>Knot Messenger</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet" />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@vortex/web",
|
||||
"name": "@knot/web",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
|
||||
|
Before Width: | Height: | Size: 493 B After Width: | Height: | Size: 493 B |
@@ -1587,11 +1587,11 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
>
|
||||
{/* Avatar */}
|
||||
<div className="relative">
|
||||
<div className="absolute inset-0 rounded-full bg-vortex-500/30 animate-call-wave" />
|
||||
<div className="absolute inset-0 rounded-full bg-knot-500/30 animate-call-wave" />
|
||||
{displayAvatar ? (
|
||||
<img src={displayAvatar} alt="" className="relative w-10 h-10 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="relative w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
|
||||
<div className="relative w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
|
||||
{initials}
|
||||
</div>
|
||||
)}
|
||||
@@ -1633,7 +1633,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
>
|
||||
{/* Ambient background glow for call modal */}
|
||||
<div className="absolute inset-0 pointer-events-none opacity-40">
|
||||
<div className="absolute top-[10%] left-[20%] w-[50vh] h-[50vh] bg-vortex-500/30 rounded-full blur-[120px] animate-float" />
|
||||
<div className="absolute top-[10%] left-[20%] w-[50vh] h-[50vh] bg-knot-500/30 rounded-full blur-[120px] animate-float" />
|
||||
<div className="absolute bottom-[10%] right-[20%] w-[50vh] h-[50vh] bg-emerald-500/20 rounded-full blur-[120px] animate-float-delayed" />
|
||||
</div>
|
||||
|
||||
@@ -1657,7 +1657,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
key={mic.deviceId}
|
||||
onClick={() => switchMicrophone(mic.deviceId)}
|
||||
className={`w-full text-left px-4 py-2.5 text-sm transition-colors ${activeMicId === mic.deviceId
|
||||
? 'text-vortex-400 bg-vortex-500/20 font-medium'
|
||||
? 'text-knot-400 bg-knot-500/20 font-medium'
|
||||
: 'text-zinc-200 hover:bg-zinc-700'
|
||||
}`}
|
||||
>
|
||||
@@ -1679,7 +1679,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
key={cam.deviceId}
|
||||
onClick={() => switchCamera(cam.deviceId)}
|
||||
className={`w-full text-left px-4 py-2.5 text-sm transition-colors ${activeCameraId === cam.deviceId
|
||||
? 'text-vortex-400 bg-vortex-500/20 font-medium'
|
||||
? 'text-knot-400 bg-knot-500/20 font-medium'
|
||||
: 'text-zinc-200 hover:bg-zinc-700'
|
||||
}`}
|
||||
>
|
||||
@@ -1704,7 +1704,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
step="0.05"
|
||||
value={remoteVolume}
|
||||
onChange={(e) => handleVolumeChange(parseFloat(e.target.value))}
|
||||
className="w-full h-1.5 rounded-full appearance-none bg-zinc-600 accent-vortex-500 cursor-pointer"
|
||||
className="w-full h-1.5 rounded-full appearance-none bg-zinc-600 accent-knot-500 cursor-pointer"
|
||||
/>
|
||||
<span className="text-xs text-zinc-300 w-8 text-right">{Math.round(remoteVolume * 100)}%</span>
|
||||
</div>
|
||||
@@ -1731,11 +1731,11 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
{!hasRemoteVideo && (
|
||||
<div className="absolute inset-0 flex flex-col items-center justify-center bg-zinc-900 z-10">
|
||||
<div className="relative mb-6">
|
||||
<div className="absolute inset-0 rounded-full bg-vortex-500/20 animate-ping" />
|
||||
<div className="absolute inset-0 rounded-full bg-knot-500/20 animate-ping" />
|
||||
{displayAvatar ? (
|
||||
<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" />
|
||||
<img src={displayAvatar} alt="" className="relative w-24 h-24 sm:w-32 sm:h-32 rounded-full object-cover border-4 border-knot-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">
|
||||
<div className="relative w-24 h-24 sm:w-32 sm:h-32 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-3xl sm:text-4xl font-bold border-4 border-knot-500/30">
|
||||
{initials}
|
||||
</div>
|
||||
)}
|
||||
@@ -1743,7 +1743,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
<VideoOff size={48} className="text-zinc-500 mb-2" />
|
||||
<span className="text-xl text-white font-medium mb-1">{displayName}</span>
|
||||
<div className="flex items-center gap-2 text-zinc-500">
|
||||
<div className="w-2 h-2 rounded-full bg-vortex-500 animate-pulse" />
|
||||
<div className="w-2 h-2 rounded-full bg-knot-500 animate-pulse" />
|
||||
<span className="text-sm animate-pulse">
|
||||
{isScreenSharing ? 'Вы транслируете экран...' : 'Ожидание потока...'}
|
||||
</span>
|
||||
@@ -1775,7 +1775,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
});
|
||||
}
|
||||
}}
|
||||
className="px-6 py-3 rounded-full bg-vortex-500 hover:bg-vortex-600 text-white font-bold shadow-2xl transition-all hover:scale-105 flex items-center gap-2"
|
||||
className="px-6 py-3 rounded-full bg-knot-500 hover:bg-knot-600 text-white font-bold shadow-2xl transition-all hover:scale-105 flex items-center gap-2"
|
||||
>
|
||||
<Volume2 size={24} />
|
||||
Включить звук и видео
|
||||
@@ -1837,8 +1837,8 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
<div className="relative mb-8 mt-4">
|
||||
{(callState === 'calling' || callState === 'connected') && (
|
||||
<>
|
||||
<div className="absolute inset-0 rounded-full bg-vortex-500/30 animate-call-wave" />
|
||||
<div className="absolute inset-0 rounded-full bg-vortex-500/20 animate-call-wave-delayed" />
|
||||
<div className="absolute inset-0 rounded-full bg-knot-500/30 animate-call-wave" />
|
||||
<div className="absolute inset-0 rounded-full bg-knot-500/20 animate-call-wave-delayed" />
|
||||
</>
|
||||
)}
|
||||
{callState === 'incoming' && (
|
||||
@@ -1851,7 +1851,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
{displayAvatar ? (
|
||||
<img src={displayAvatar} alt="" className="w-24 h-24 sm:w-32 sm:h-32 rounded-full object-cover 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">
|
||||
<div className="w-24 h-24 sm:w-32 sm:h-32 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-bold text-3xl sm:text-4xl shadow-inner">
|
||||
{initials}
|
||||
</div>
|
||||
)}
|
||||
@@ -1954,7 +1954,7 @@ export default function CallModal({ isOpen, onClose, targetUser, callType: initi
|
||||
remoteScreenSharing
|
||||
? 'bg-zinc-800 text-zinc-600 cursor-not-allowed hidden sm:flex'
|
||||
: isScreenSharing
|
||||
? 'bg-vortex-500/30 text-vortex-400'
|
||||
? 'bg-knot-500/30 text-knot-400'
|
||||
: 'bg-white/10 text-white hover:bg-white/20'
|
||||
}`}
|
||||
title={remoteScreenSharing ? (t('remoteSharingScreen' as any) as string) : isScreenSharing ? t('stopScreenShare') : t('screenShare')}
|
||||
|
||||
@@ -147,7 +147,7 @@ function ChatListItem({ chat, isActive }: ChatListItemProps) {
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
{isPinned && <Pin size={12} className="text-vortex-400 flex-shrink-0 rotate-45" />}
|
||||
{isPinned && <Pin size={12} className="text-knot-400 flex-shrink-0 rotate-45" />}
|
||||
<span className="text-sm font-medium text-white truncate">{chatName}</span>
|
||||
</div>
|
||||
{timeStr && <span className="text-xs text-zinc-500 flex-shrink-0 ml-2">{timeStr}</span>}
|
||||
@@ -157,13 +157,13 @@ function ChatListItem({ chat, isActive }: ChatListItemProps) {
|
||||
{isMine && lastMessage && !lastMessage.isDeleted && (
|
||||
<span className="flex-shrink-0">
|
||||
{isRead ? (
|
||||
<CheckCheck size={14} className="text-vortex-400" />
|
||||
<CheckCheck size={14} className="text-knot-400" />
|
||||
) : (
|
||||
<Check size={14} className="text-zinc-500" />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
<p className={`text-xs truncate ${isTyping ? 'text-vortex-400 font-medium' : draft ? 'text-red-400' : 'text-zinc-400'}`}>
|
||||
<p className={`text-xs truncate ${isTyping ? 'text-knot-400 font-medium' : draft ? 'text-red-400' : 'text-zinc-400'}`}>
|
||||
{isTyping ? t('typing') : draft ? <><span className="font-medium">{t('draft')} </span>{stripMarkdown(draft)}</> : previewText}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -345,7 +345,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
transition={{ duration: 0.6, delay: 0.1, ease: [0.16, 1, 0.3, 1] }}
|
||||
className="text-3xl font-bold text-transparent bg-clip-text bg-gradient-to-r from-accent via-fuchsia-400 to-indigo-400 mb-4 drop-shadow-lg tracking-tight"
|
||||
>
|
||||
SelfHost Messenger
|
||||
Knot Messenger
|
||||
</motion.h2>
|
||||
<motion.p
|
||||
initial={{ y: 20, opacity: 0 }}
|
||||
@@ -743,8 +743,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
const el = document.getElementById(`msg-${msg.id}`);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('bg-vortex-500/20');
|
||||
setTimeout(() => el.classList.remove('bg-vortex-500/20'), 2000);
|
||||
el.classList.add('bg-knot-500/20');
|
||||
setTimeout(() => el.classList.remove('bg-knot-500/20'), 2000);
|
||||
}
|
||||
setShowSearch(false);
|
||||
setSearchText('');
|
||||
@@ -752,7 +752,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
}}
|
||||
>
|
||||
<div className="flex items-center gap-2 mb-0.5">
|
||||
<span className="text-xs font-medium text-vortex-400">
|
||||
<span className="text-xs font-medium text-knot-400">
|
||||
{msg.sender?.displayName || msg.sender?.username}
|
||||
</span>
|
||||
<span className="text-xs text-zinc-600">
|
||||
@@ -790,15 +790,15 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
const el = document.getElementById(`msg-${pinnedMsg.id}`);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('bg-vortex-500/20');
|
||||
setTimeout(() => el.classList.remove('bg-vortex-500/20'), 2000);
|
||||
el.classList.add('bg-knot-500/20');
|
||||
setTimeout(() => el.classList.remove('bg-knot-500/20'), 2000);
|
||||
}
|
||||
}}
|
||||
className="flex items-center gap-3 px-4 py-2 border-b border-border bg-surface-secondary/60 hover:bg-surface-hover transition-colors text-left w-full flex-shrink-0"
|
||||
>
|
||||
<Pin size={16} className="text-vortex-400 flex-shrink-0 rotate-45" />
|
||||
<Pin size={16} className="text-knot-400 flex-shrink-0 rotate-45" />
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-xs font-medium text-vortex-400">{t('pinnedMessage')}</p>
|
||||
<p className="text-xs font-medium text-knot-400">{t('pinnedMessage')}</p>
|
||||
<p className="text-sm text-zinc-300 truncate">
|
||||
{pinnedMsg.content || (pinnedMsg.media?.length > 0 ? t('media') : '...')}
|
||||
</p>
|
||||
@@ -825,7 +825,7 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
>
|
||||
{isLoadingMessages ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<div className="w-6 h-6 border-2 border-vortex-500 border-t-transparent rounded-full animate-spin" />
|
||||
<div className="w-6 h-6 border-2 border-knot-500 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
) : chatMessages.length === 0 ? (
|
||||
<div className="flex items-center justify-center h-full">
|
||||
@@ -923,8 +923,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
const el = document.getElementById(`msg-${msgId}`);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('bg-vortex-500/20');
|
||||
setTimeout(() => el.classList.remove('bg-vortex-500/20'), 2000);
|
||||
el.classList.add('bg-knot-500/20');
|
||||
setTimeout(() => el.classList.remove('bg-knot-500/20'), 2000);
|
||||
setProfileUserId(null);
|
||||
}
|
||||
}}
|
||||
@@ -943,8 +943,8 @@ export default function ChatView({ onStartCall, onStartGroupCall }: { onStartCal
|
||||
const el = document.getElementById(`msg-${msgId}`);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
||||
el.classList.add('bg-vortex-500/20');
|
||||
setTimeout(() => el.classList.remove('bg-vortex-500/20'), 2000);
|
||||
el.classList.add('bg-knot-500/20');
|
||||
setTimeout(() => el.classList.remove('bg-knot-500/20'), 2000);
|
||||
setShowGroupSettings(false);
|
||||
}
|
||||
}}
|
||||
|
||||
@@ -224,7 +224,7 @@ export default function DatePicker({ value, onChange }: DatePickerProps) {
|
||||
isSelected(day)
|
||||
? 'bg-accent text-white font-semibold shadow-lg shadow-accent/30'
|
||||
: isToday(day)
|
||||
? 'text-vortex-400 font-semibold ring-1 ring-vortex-500/50'
|
||||
? 'text-knot-400 font-semibold ring-1 ring-knot-500/50'
|
||||
: 'text-zinc-300 hover:bg-surface-hover'
|
||||
}`}
|
||||
>
|
||||
@@ -261,7 +261,7 @@ export default function DatePicker({ value, onChange }: DatePickerProps) {
|
||||
isSelectedMonth
|
||||
? 'bg-accent text-white shadow-lg shadow-accent/30'
|
||||
: isCurrentMonth
|
||||
? 'text-vortex-400 ring-1 ring-vortex-500/50'
|
||||
? 'text-knot-400 ring-1 ring-knot-500/50'
|
||||
: 'text-zinc-300 hover:bg-surface-hover'
|
||||
}`}
|
||||
>
|
||||
@@ -294,7 +294,7 @@ export default function DatePicker({ value, onChange }: DatePickerProps) {
|
||||
isSelectedYear
|
||||
? 'bg-accent text-white shadow-lg shadow-accent/30'
|
||||
: isCurrentYear
|
||||
? 'text-vortex-400 ring-1 ring-vortex-500/50'
|
||||
? 'text-knot-400 ring-1 ring-knot-500/50'
|
||||
: 'text-zinc-300 hover:bg-surface-hover'
|
||||
}`}
|
||||
>
|
||||
@@ -323,7 +323,7 @@ export default function DatePicker({ value, onChange }: DatePickerProps) {
|
||||
onChange(`${today.getFullYear()}-${m}-${d}`);
|
||||
setOpen(false);
|
||||
}}
|
||||
className="text-xs text-vortex-400 hover:text-vortex-300 transition-colors"
|
||||
className="text-xs text-knot-400 hover:text-knot-300 transition-colors"
|
||||
>
|
||||
{t('today')}
|
||||
</button>
|
||||
|
||||
@@ -182,7 +182,7 @@ export default function EmojiPicker({ onSelect, onSelectGif, onClose }: EmojiPic
|
||||
<div className="flex-1 flex flex-col items-center justify-center p-6 text-center">
|
||||
<p className="text-sm text-zinc-400 mb-2">Klipy API Key required</p>
|
||||
<p className="text-xs text-zinc-500 mb-3">{t('openConsoleRun')}</p>
|
||||
<code className="text-xs bg-black/30 px-3 py-1.5 rounded-lg text-vortex-400">
|
||||
<code className="text-xs bg-black/30 px-3 py-1.5 rounded-lg text-knot-400">
|
||||
VITE_KLIPY_API_KEY in .env
|
||||
</code>
|
||||
</div>
|
||||
|
||||
@@ -68,7 +68,7 @@ export default function ForwardModal({ onClose, onForward }: ForwardModalProps)
|
||||
placeholder={t('searchChats') || 'Поиск чатов'}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
className="w-full bg-black/20 border border-white/10 rounded-xl py-2.5 pl-10 pr-4 text-white placeholder-zinc-500 focus:outline-none focus:border-vortex-500 transition-colors"
|
||||
className="w-full bg-black/20 border border-white/10 rounded-xl py-2.5 pl-10 pr-4 text-white placeholder-zinc-500 focus:outline-none focus:border-knot-500 transition-colors"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -836,7 +836,7 @@ export default function GroupCallModal({ isOpen, onClose, chatId, chatName, call
|
||||
>
|
||||
<div className="absolute inset-0 pointer-events-none opacity-40">
|
||||
<div className="absolute top-[10%] left-[20%] w-[50vh] h-[50vh] bg-emerald-500/30 rounded-full blur-[120px] animate-float" />
|
||||
<div className="absolute bottom-[10%] right-[20%] w-[50vh] h-[50vh] bg-vortex-500/20 rounded-full blur-[120px] animate-float-delayed" />
|
||||
<div className="absolute bottom-[10%] right-[20%] w-[50vh] h-[50vh] bg-knot-500/20 rounded-full blur-[120px] animate-float-delayed" />
|
||||
</div>
|
||||
|
||||
<motion.div
|
||||
@@ -860,7 +860,7 @@ export default function GroupCallModal({ isOpen, onClose, chatId, chatName, call
|
||||
step="0.05"
|
||||
value={remoteVolume}
|
||||
onChange={e => handleVolumeChange(parseFloat(e.target.value))}
|
||||
className="w-full h-1.5 rounded-full appearance-none bg-zinc-600 accent-vortex-500 cursor-pointer"
|
||||
className="w-full h-1.5 rounded-full appearance-none bg-zinc-600 accent-knot-500 cursor-pointer"
|
||||
/>
|
||||
<span className="text-xs text-zinc-300 w-8 text-right">{Math.round(remoteVolume * 100)}%</span>
|
||||
</div>
|
||||
@@ -878,7 +878,7 @@ export default function GroupCallModal({ isOpen, onClose, chatId, chatName, call
|
||||
key={mic.deviceId}
|
||||
onClick={() => switchMicrophone(mic.deviceId)}
|
||||
className={`w-full text-left px-4 py-2.5 text-sm transition-colors ${activeMicId === mic.deviceId
|
||||
? 'text-vortex-400 bg-vortex-500/20 font-medium'
|
||||
? 'text-knot-400 bg-knot-500/20 font-medium'
|
||||
: 'text-zinc-200 hover:bg-zinc-700'
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -282,7 +282,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
{groups.map((g, i) => (
|
||||
<div key={i}>
|
||||
<div className="sticky top-0 z-10 bg-black/60 backdrop-blur-md px-3 py-1.5 mb-1.5 shadow-sm border-y border-white/5">
|
||||
<span className="text-[10px] font-bold text-vortex-300 uppercase tracking-widest">{g.dateStr}</span>
|
||||
<span className="text-[10px] font-bold text-knot-300 uppercase tracking-widest">{g.dateStr}</span>
|
||||
</div>
|
||||
<div className={gridClass || "flex flex-col gap-0.5"}>
|
||||
{g.items.map(({item, idx}) => renderItem(item, idx))}
|
||||
@@ -339,7 +339,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
{/* Avatar */}
|
||||
<div className="flex-shrink-0 flex flex-col items-center py-6 px-6 overflow-y-auto max-h-[50%] custom-scrollbar">
|
||||
<div className="relative group">
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-40 h-40 bg-vortex-500/20 rounded-full blur-[40px] pointer-events-none" />
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-40 h-40 bg-knot-500/20 rounded-full blur-[40px] pointer-events-none" />
|
||||
<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">
|
||||
{chat.avatar ? (
|
||||
<img
|
||||
@@ -348,7 +348,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
className="w-32 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-32 h-32 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-bold text-4xl shadow-inner">
|
||||
{initials}
|
||||
</div>
|
||||
)}
|
||||
@@ -420,7 +420,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
{chat.name || t('group')}
|
||||
</h3>
|
||||
{isAdmin && (
|
||||
<Edit3 size={16} className="text-vortex-400 opacity-0 group-hover/name:opacity-100 transition-opacity" />
|
||||
<Edit3 size={16} className="text-knot-400 opacity-0 group-hover/name:opacity-100 transition-opacity" />
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -468,7 +468,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
</p>
|
||||
{isAdmin && (
|
||||
<div className="absolute top-3 right-3 opacity-0 group-hover/desc:opacity-100 transition-opacity">
|
||||
<Edit3 size={14} className="text-vortex-400" />
|
||||
<Edit3 size={14} className="text-knot-400" />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -489,7 +489,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
className={`flex-1 flex flex-col items-center justify-center gap-1 py-2 text-[10px] font-bold uppercase tracking-widest transition-all ${
|
||||
activeTab === tab.key
|
||||
? 'bg-white/5 text-vortex-400'
|
||||
? 'bg-white/5 text-knot-400'
|
||||
: 'text-zinc-500 hover:text-zinc-300'
|
||||
}`}
|
||||
>
|
||||
@@ -521,7 +521,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
setTimeout(() => searchInputRef.current?.focus(), 100);
|
||||
}
|
||||
}}
|
||||
className="flex items-center gap-1 text-xs text-vortex-400 hover:text-vortex-300 transition-colors"
|
||||
className="flex items-center gap-1 text-xs text-knot-400 hover:text-knot-300 transition-colors"
|
||||
>
|
||||
<UserPlus size={14} />
|
||||
{t('addMember')}
|
||||
@@ -563,7 +563,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
{u.avatar ? (
|
||||
<img src={getMediaUrl(u.avatar)} alt="" className="w-8 h-8 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-xs font-bold">
|
||||
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-xs font-bold">
|
||||
{(u.displayName || u.username || '?')[0].toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
@@ -571,7 +571,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
<p className="text-sm text-white truncate">{u.displayName || u.username}</p>
|
||||
<p className="text-xs text-zinc-500">@{u.username}</p>
|
||||
</div>
|
||||
<UserPlus size={14} className="text-vortex-400 flex-shrink-0" />
|
||||
<UserPlus size={14} className="text-knot-400 flex-shrink-0" />
|
||||
</button>
|
||||
))}
|
||||
{searchQuery.trim() && !isSearching && searchResults.length === 0 && (
|
||||
@@ -602,7 +602,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
{member.user.avatar ? (
|
||||
<img src={getMediaUrl(member.user.avatar)} alt="" className="w-9 h-9 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="w-9 h-9 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-xs font-bold">
|
||||
<div className="w-9 h-9 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-xs font-bold">
|
||||
{(member.user.displayName || member.user.username || '?')[0].toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
@@ -698,7 +698,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
download={m.filename || 'file'}
|
||||
className="flex items-center gap-3 px-4 py-3 hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<div className="w-8 h-8 rounded-lg bg-vortex-500/10 flex items-center justify-center flex-shrink-0 text-vortex-400">
|
||||
<div className="w-8 h-8 rounded-lg bg-knot-500/10 flex items-center justify-center flex-shrink-0 text-knot-400">
|
||||
<FileText size={16} />
|
||||
</div>
|
||||
|
||||
@@ -734,7 +734,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
href={link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 text-[13px] text-vortex-400 hover:underline truncate mb-1"
|
||||
className="flex items-center gap-2 text-[13px] text-knot-400 hover:underline truncate mb-1"
|
||||
>
|
||||
<ExternalLink size={12} className="flex-shrink-0" />
|
||||
{link}
|
||||
@@ -743,7 +743,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
{msg.content && <p className="text-[11px] text-zinc-500 line-clamp-1">{msg.content}</p>}
|
||||
<button
|
||||
onClick={() => onGoToMessage?.(msg.id)}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 px-3 py-1.5 rounded-lg bg-black/40 hover:bg-vortex-500/20 text-zinc-300 hover:text-white text-[11px] font-medium opacity-0 group-hover:opacity-100 transition-all shadow-md z-10"
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 px-3 py-1.5 rounded-lg bg-black/40 hover:bg-knot-500/20 text-zinc-300 hover:text-white text-[11px] font-medium opacity-0 group-hover:opacity-100 transition-all shadow-md z-10"
|
||||
>
|
||||
{t('showInChat')}
|
||||
</button>
|
||||
@@ -828,7 +828,7 @@ export default function GroupSettings({ chat, onClose, onGoToMessage }: GroupSet
|
||||
max={3}
|
||||
step={0.1}
|
||||
onChange={(e) => setZoom(Number(e.target.value))}
|
||||
className="flex-1 accent-vortex-500"
|
||||
className="flex-1 accent-knot-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ export default function LinkPreview({ url }: LinkPreviewProps) {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="mt-2 text-xs text-vortex-400 opacity-70 italic border-l-[3px] border-vortex-500/50 pl-2">
|
||||
<div className="mt-2 text-xs text-knot-400 opacity-70 italic border-l-[3px] border-knot-500/50 pl-2">
|
||||
Загрузка предпросмотра...
|
||||
</div>
|
||||
);
|
||||
@@ -78,11 +78,11 @@ export default function LinkPreview({ url }: LinkPreviewProps) {
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="block mt-2 border-l-[3px] border-vortex-500 bg-black/20 rounded-r-lg overflow-hidden hover:bg-black/30 transition-colors"
|
||||
className="block mt-2 border-l-[3px] border-knot-500 bg-black/20 rounded-r-lg overflow-hidden hover:bg-black/30 transition-colors"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div className="p-2.5 flex flex-col gap-1.5">
|
||||
<div className="flex items-center gap-1.5 text-xs font-semibold text-vortex-400">
|
||||
<div className="flex items-center gap-1.5 text-xs font-semibold text-knot-400">
|
||||
{data.logo?.url && <img src={data.logo.url} alt="" className="w-3.5 h-3.5 rounded-sm object-cover" />}
|
||||
<span className="truncate">{domain}</span>
|
||||
</div>
|
||||
|
||||
@@ -360,7 +360,7 @@ function MessageBubble({
|
||||
<div
|
||||
ref={bubbleRef}
|
||||
className={`flex ${isMine ? 'justify-end' : 'justify-start'} group mb-0.5 relative transition-colors duration-200 ${selectionMode ? 'px-4 -mx-4 cursor-pointer hover:bg-white/5 rounded-xl' : ''
|
||||
} ${isSelected ? 'bg-vortex-500/10 hover:bg-vortex-500/20' : ''}`}
|
||||
} ${isSelected ? 'bg-knot-500/10 hover:bg-knot-500/20' : ''}`}
|
||||
onClick={() => {
|
||||
if (selectionMode) onToggleSelect?.(message.id);
|
||||
}}
|
||||
@@ -368,7 +368,7 @@ function MessageBubble({
|
||||
>
|
||||
{selectionMode && (
|
||||
<div className="absolute left-1 top-1/2 -translate-y-1/2 w-5 h-5 rounded-full border border-white/30 flex items-center justify-center transition-colors">
|
||||
{isSelected && <div className="w-5 h-5 rounded-full bg-vortex-500 flex items-center justify-center">
|
||||
{isSelected && <div className="w-5 h-5 rounded-full bg-knot-500 flex items-center justify-center">
|
||||
<Check size={12} className="text-white" />
|
||||
</div>}
|
||||
</div>
|
||||
@@ -381,7 +381,7 @@ function MessageBubble({
|
||||
{senderAvatar ? (
|
||||
<img src={senderAvatar} alt="" className="w-8 h-8 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-xs font-semibold">
|
||||
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-xs font-semibold">
|
||||
{senderName[0]?.toUpperCase() || '?'}
|
||||
</div>
|
||||
)}
|
||||
@@ -393,7 +393,7 @@ function MessageBubble({
|
||||
<div className={`max-w-[65%] ${isMine ? 'items-end' : 'items-start'} flex flex-col`}>
|
||||
{!isMine && showAvatar && (
|
||||
<button
|
||||
className="text-xs font-medium text-vortex-400 ml-3 mb-0.5 hover:underline"
|
||||
className="text-xs font-medium text-knot-400 ml-3 mb-0.5 hover:underline"
|
||||
onClick={() => onViewProfile?.(message.senderId)}
|
||||
>
|
||||
{senderName}
|
||||
@@ -403,7 +403,7 @@ function MessageBubble({
|
||||
{/* Reply */}
|
||||
{message.replyTo && (
|
||||
<div
|
||||
className="mx-3 mb-1 px-3 py-1.5 rounded-lg border-l-2 border-vortex-500 bg-vortex-500/10 max-w-full cursor-pointer hover:bg-vortex-500/20 transition-colors"
|
||||
className="mx-3 mb-1 px-3 py-1.5 rounded-lg border-l-2 border-knot-500 bg-knot-500/10 max-w-full cursor-pointer hover:bg-knot-500/20 transition-colors"
|
||||
onClick={() => {
|
||||
const el = document.getElementById(`msg-${message.replyToId}`);
|
||||
if (el) {
|
||||
@@ -413,7 +413,7 @@ function MessageBubble({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<p className="text-xs font-medium text-vortex-400 truncate">
|
||||
<p className="text-xs font-medium text-knot-400 truncate">
|
||||
{message.replyTo.sender?.displayName || message.replyTo.sender?.username}
|
||||
</p>
|
||||
<div className="flex items-center gap-1">
|
||||
@@ -458,7 +458,7 @@ function MessageBubble({
|
||||
) : message.storyMediaType === 'image' ? (
|
||||
<img src={getMediaUrl(message.storyMediaUrl)} className="w-full h-full object-cover" alt="" />
|
||||
) : (
|
||||
<div className="w-full h-full flex items-center justify-center bg-vortex-500/20"><FileText size={10} className="text-vortex-400" /></div>
|
||||
<div className="w-full h-full flex items-center justify-center bg-knot-500/20"><FileText size={10} className="text-knot-400" /></div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
@@ -555,13 +555,13 @@ function MessageBubble({
|
||||
/>
|
||||
<button
|
||||
onClick={toggleAudio}
|
||||
className={`w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0 ${isMine ? 'bg-white/20 hover:bg-white/30' : 'bg-vortex-500/20 hover:bg-vortex-500/30'
|
||||
className={`w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0 ${isMine ? 'bg-white/20 hover:bg-white/30' : 'bg-knot-500/20 hover:bg-knot-500/30'
|
||||
} transition-colors`}
|
||||
>
|
||||
{isPlaying ? (
|
||||
<Pause size={16} className={isMine ? 'text-white' : 'text-vortex-400'} />
|
||||
<Pause size={16} className={isMine ? 'text-white' : 'text-knot-400'} />
|
||||
) : (
|
||||
<Play size={16} className={`${isMine ? 'text-white' : 'text-vortex-400'} ml-0.5`} />
|
||||
<Play size={16} className={`${isMine ? 'text-white' : 'text-knot-400'} ml-0.5`} />
|
||||
)}
|
||||
</button>
|
||||
<div className="flex-1 min-w-0">
|
||||
@@ -586,7 +586,7 @@ function MessageBubble({
|
||||
<div
|
||||
key={i}
|
||||
className={`flex-1 rounded-full transition-colors duration-150 ${isActive
|
||||
? isMine ? 'bg-white/80' : 'bg-vortex-400'
|
||||
? isMine ? 'bg-white/80' : 'bg-knot-400'
|
||||
: isMine ? 'bg-white/20' : 'bg-white/10'
|
||||
}`}
|
||||
style={{ height: `${barHeight}%` }}
|
||||
@@ -610,7 +610,7 @@ function MessageBubble({
|
||||
<div className="min-w-[220px]">
|
||||
{audioMedia?.filename && (
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<Volume2 size={14} className={isMine ? 'text-white/60' : 'text-vortex-400'} />
|
||||
<Volume2 size={14} className={isMine ? 'text-white/60' : 'text-knot-400'} />
|
||||
<span className={`text-xs truncate ${isMine ? 'text-white/70' : 'text-zinc-400'}`}>{audioMedia.filename}</span>
|
||||
</div>
|
||||
)}
|
||||
@@ -623,13 +623,13 @@ function MessageBubble({
|
||||
/>
|
||||
<button
|
||||
onClick={toggleAudio}
|
||||
className={`w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0 ${isMine ? 'bg-white/20 hover:bg-white/30' : 'bg-vortex-500/20 hover:bg-vortex-500/30'
|
||||
className={`w-9 h-9 rounded-full flex items-center justify-center flex-shrink-0 ${isMine ? 'bg-white/20 hover:bg-white/30' : 'bg-knot-500/20 hover:bg-knot-500/30'
|
||||
} transition-colors`}
|
||||
>
|
||||
{isPlaying ? (
|
||||
<Pause size={16} className={isMine ? 'text-white' : 'text-vortex-400'} />
|
||||
<Pause size={16} className={isMine ? 'text-white' : 'text-knot-400'} />
|
||||
) : (
|
||||
<Play size={16} className={`${isMine ? 'text-white' : 'text-vortex-400'} ml-0.5`} />
|
||||
<Play size={16} className={`${isMine ? 'text-white' : 'text-knot-400'} ml-0.5`} />
|
||||
)}
|
||||
</button>
|
||||
<div className="flex-1 min-w-0">
|
||||
@@ -643,7 +643,7 @@ function MessageBubble({
|
||||
<div
|
||||
key={i}
|
||||
className={`flex-1 rounded-full transition-colors duration-150 ${isActive
|
||||
? isMine ? 'bg-white/80' : 'bg-vortex-400'
|
||||
? isMine ? 'bg-white/80' : 'bg-knot-400'
|
||||
: isMine ? 'bg-white/20' : 'bg-white/10'
|
||||
}`}
|
||||
style={{ height: `${barHeight}%` }}
|
||||
@@ -676,9 +676,9 @@ function MessageBubble({
|
||||
className={`flex items-center gap-3 p-2 rounded-xl ${isMine ? 'bg-white/10 hover:bg-white/15' : 'bg-surface-tertiary hover:bg-surface-hover'
|
||||
} transition-colors mb-1`}
|
||||
>
|
||||
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${isMine ? 'bg-white/20' : 'bg-vortex-500/20'
|
||||
<div className={`w-10 h-10 rounded-lg flex items-center justify-center ${isMine ? 'bg-white/20' : 'bg-knot-500/20'
|
||||
}`}>
|
||||
<FileText size={20} className={isMine ? 'text-white' : 'text-vortex-400'} />
|
||||
<FileText size={20} className={isMine ? 'text-white' : 'text-knot-400'} />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm truncate">{m.filename || t('fileLabel')}</p>
|
||||
@@ -743,7 +743,7 @@ function MessageBubble({
|
||||
key={emoji}
|
||||
onClick={() => handleReaction(emoji)}
|
||||
className={`flex items-center gap-1 px-2 py-0.5 rounded-full text-xs transition-colors ${data.isMine
|
||||
? 'bg-vortex-500/30 border border-vortex-500/50'
|
||||
? 'bg-knot-500/30 border border-knot-500/50'
|
||||
: 'bg-surface-tertiary border border-border hover:border-zinc-600'
|
||||
}`}
|
||||
title={data.users.join(', ')}
|
||||
@@ -763,7 +763,7 @@ function MessageBubble({
|
||||
{senderAvatar ? (
|
||||
<img src={senderAvatar} alt="" className="w-8 h-8 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-xs font-semibold">
|
||||
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-xs font-semibold">
|
||||
{senderName[0]?.toUpperCase() || '?'}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -574,9 +574,9 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ opacity: 0 }}
|
||||
className="absolute inset-0 z-50 rounded-[2rem] mx-6 mb-6 mt-2 bg-vortex-500/10 border-2 border-dashed border-vortex-400 backdrop-blur-sm flex items-center justify-center pointer-events-none"
|
||||
className="absolute inset-0 z-50 rounded-[2rem] mx-6 mb-6 mt-2 bg-knot-500/10 border-2 border-dashed border-knot-400 backdrop-blur-sm flex items-center justify-center pointer-events-none"
|
||||
>
|
||||
<div className="flex flex-col items-center gap-2 text-vortex-300">
|
||||
<div className="flex flex-col items-center gap-2 text-knot-300">
|
||||
<FileText size={32} className="animate-bounce" />
|
||||
<p className="font-semibold">{t('dropFileHere')}</p>
|
||||
</div>
|
||||
@@ -594,16 +594,16 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
className="mb-2 max-w-3xl mx-auto overflow-hidden px-1.5"
|
||||
>
|
||||
<div className="flex items-center gap-3 px-4 py-2.5 bg-white/[0.04] backdrop-blur-2xl border border-white/10 rounded-2xl relative shadow-xl">
|
||||
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-3/5 bg-gradient-to-b from-vortex-400 to-purple-500 rounded-r-md" />
|
||||
<div className="absolute left-0 top-1/2 -translate-y-1/2 w-1 h-3/5 bg-gradient-to-b from-knot-400 to-purple-500 rounded-r-md" />
|
||||
<div className="w-6 h-6 rounded-full bg-white/5 flex items-center justify-center flex-shrink-0">
|
||||
{editingMessage ? (
|
||||
<Pencil size={12} className="text-vortex-400" />
|
||||
<Pencil size={12} className="text-knot-400" />
|
||||
) : (
|
||||
<Reply size={12} className="text-vortex-400" />
|
||||
<Reply size={12} className="text-knot-400" />
|
||||
)}
|
||||
</div>
|
||||
<div className="flex-1 min-w-0 flex flex-col justify-center">
|
||||
<p className="text-xs font-semibold text-vortex-400 mb-0.5">
|
||||
<p className="text-xs font-semibold text-knot-400 mb-0.5">
|
||||
{editingMessage
|
||||
? t('editing')
|
||||
: `${t('replyTo')} ${replyTo?.sender?.displayName || replyTo?.sender?.username || ''}`}
|
||||
@@ -652,8 +652,8 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
className="w-full h-full object-cover"
|
||||
/>
|
||||
) : att.type === 'video' ? (
|
||||
<div className="w-full h-full bg-vortex-500/20 flex items-center justify-center">
|
||||
<ImageIcon size={20} className="text-vortex-400" />
|
||||
<div className="w-full h-full bg-knot-500/20 flex items-center justify-center">
|
||||
<ImageIcon size={20} className="text-knot-400" />
|
||||
</div>
|
||||
) : att.type === 'audio' ? (
|
||||
<div className="w-full h-full bg-emerald-500/20 flex items-center justify-center">
|
||||
@@ -710,7 +710,7 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
{liveBars.map((h, i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="flex-1 bg-vortex-400/60 rounded-full transition-all duration-100"
|
||||
className="flex-1 bg-knot-400/60 rounded-full transition-all duration-100"
|
||||
style={{ height: `${h}%` }}
|
||||
/>
|
||||
))}
|
||||
@@ -724,7 +724,7 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
</button>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-end gap-1.5 bg-white/[0.04] backdrop-blur-[40px] rounded-[2rem] border border-white/[0.08] p-2 w-full max-w-3xl mx-auto transition-all duration-300 hover:bg-white/[0.06] focus-within:bg-white/[0.08] shadow-[0_8px_32px_rgba(0,0,0,0.3)] focus-within:shadow-[0_8px_40px_rgba(99,102,241,0.15)] focus-within:border-vortex-500/30 group">
|
||||
<div className="flex items-end gap-1.5 bg-white/[0.04] backdrop-blur-[40px] rounded-[2rem] border border-white/[0.08] p-2 w-full max-w-3xl mx-auto transition-all duration-300 hover:bg-white/[0.06] focus-within:bg-white/[0.08] shadow-[0_8px_32px_rgba(0,0,0,0.3)] focus-within:shadow-[0_8px_40px_rgba(99,102,241,0.15)] focus-within:border-knot-500/30 group">
|
||||
{/* Attach */}
|
||||
<div className="relative mb-0.5 ml-1 flex-shrink-0 self-center">
|
||||
<button
|
||||
@@ -747,8 +747,8 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
onClick={() => imageInputRef.current?.click()}
|
||||
className="flex items-center gap-4 w-full px-3 py-3 rounded-xl text-sm font-medium text-zinc-200 hover:bg-white/5 hover:text-white transition-all group"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-400/20 to-purple-500/20 flex items-center justify-center ring-1 ring-vortex-400/30 group-hover:scale-110 transition-transform shadow-inner">
|
||||
<ImageIcon size={18} className="text-vortex-400" />
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-400/20 to-purple-500/20 flex items-center justify-center ring-1 ring-knot-400/30 group-hover:scale-110 transition-transform shadow-inner">
|
||||
<ImageIcon size={18} className="text-knot-400" />
|
||||
</div>
|
||||
{t('photoVideo')}
|
||||
</button>
|
||||
@@ -805,7 +805,7 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
{m.user.avatar ? (
|
||||
<img src={m.user.avatar} className="w-7 h-7 rounded-full object-cover" alt="" />
|
||||
) : (
|
||||
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-xs font-semibold">
|
||||
<div className="w-7 h-7 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-xs font-semibold">
|
||||
{(m.user.displayName || m.user.username)[0]?.toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
@@ -923,7 +923,7 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
<ChevronLeft size={16} />
|
||||
</button>
|
||||
)}
|
||||
<Clock size={16} className="text-vortex-400" />
|
||||
<Clock size={16} className="text-knot-400" />
|
||||
<span className="text-sm font-medium text-zinc-200">{t('scheduleMessage')}</span>
|
||||
</div>
|
||||
|
||||
@@ -984,7 +984,7 @@ export default function MessageInput({ chatId }: MessageInputProps) {
|
||||
setScheduleMinute(String(now.getMinutes()).padStart(2, '0'));
|
||||
setScheduleStep('custom');
|
||||
}}
|
||||
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm text-vortex-400 hover:bg-white/10 transition-colors text-left"
|
||||
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl text-sm text-knot-400 hover:bg-white/10 transition-colors text-left"
|
||||
>
|
||||
<Calendar size={15} className="flex-shrink-0" />
|
||||
{t('scheduleCustom')}
|
||||
@@ -1197,7 +1197,7 @@ function ScheduleCalendar({
|
||||
: isPast(day)
|
||||
? 'text-zinc-600 cursor-not-allowed'
|
||||
: isToday(day)
|
||||
? 'text-vortex-400 font-semibold ring-1 ring-vortex-500/50'
|
||||
? 'text-knot-400 font-semibold ring-1 ring-knot-500/50'
|
||||
: 'text-zinc-300 hover:bg-white/10'
|
||||
}`}
|
||||
>
|
||||
@@ -1217,7 +1217,7 @@ function ScheduleCalendar({
|
||||
<select
|
||||
value={hour}
|
||||
onChange={(e) => setHour(e.target.value)}
|
||||
className="flex-1 bg-white/5 border border-white/10 rounded-lg px-2 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-vortex-500/50 appearance-none text-center"
|
||||
className="flex-1 bg-white/5 border border-white/10 rounded-lg px-2 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-knot-500/50 appearance-none text-center"
|
||||
>
|
||||
{Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0')).map((h) => (
|
||||
<option key={h} value={h} className="bg-zinc-800">{h}</option>
|
||||
@@ -1227,7 +1227,7 @@ function ScheduleCalendar({
|
||||
<select
|
||||
value={minute}
|
||||
onChange={(e) => setMinute(e.target.value)}
|
||||
className="flex-1 bg-white/5 border border-white/10 rounded-lg px-2 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-vortex-500/50 appearance-none text-center"
|
||||
className="flex-1 bg-white/5 border border-white/10 rounded-lg px-2 py-1.5 text-sm text-zinc-200 focus:outline-none focus:border-knot-500/50 appearance-none text-center"
|
||||
>
|
||||
{Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0')).map((m) => (
|
||||
<option key={m} value={m} className="bg-zinc-800">{m}</option>
|
||||
|
||||
@@ -163,12 +163,12 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
|
||||
{selectedUsers.map((u) => (
|
||||
<div
|
||||
key={u.id}
|
||||
className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-vortex-500/20 border border-vortex-500/30"
|
||||
className="flex items-center gap-2 px-3 py-1.5 rounded-full bg-knot-500/20 border border-knot-500/30"
|
||||
>
|
||||
{u.avatar ? (
|
||||
<img src={u.avatar} alt="" className="w-5 h-5 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="w-5 h-5 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white text-[9px] font-semibold">
|
||||
<div className="w-5 h-5 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white text-[9px] font-semibold">
|
||||
{(u.displayName || u.username)?.[0]?.toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
@@ -207,7 +207,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
|
||||
onClick={() => setMode('group-select')}
|
||||
className="w-full flex items-center gap-3 px-3 py-2.5 rounded-xl bg-surface-tertiary hover:bg-surface-hover transition-colors border border-border"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center">
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center">
|
||||
<Users size={18} className="text-white" />
|
||||
</div>
|
||||
<div className="text-left">
|
||||
@@ -224,7 +224,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
|
||||
<button
|
||||
key={u.id}
|
||||
onClick={() => setSelectedUsers((prev) => prev.filter((p) => p.id !== u.id))}
|
||||
className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-vortex-500/20 border border-vortex-500/30 text-xs text-white hover:bg-vortex-500/30 transition-colors"
|
||||
className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-knot-500/20 border border-knot-500/30 text-xs text-white hover:bg-knot-500/30 transition-colors"
|
||||
>
|
||||
{(u.displayName || u.username)}
|
||||
<X size={11} />
|
||||
@@ -254,7 +254,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
|
||||
<div className="max-h-72 overflow-y-auto px-2 pb-4">
|
||||
{isLoading ? (
|
||||
<div className="flex justify-center py-8">
|
||||
<div className="w-5 h-5 border-2 border-vortex-500 border-t-transparent rounded-full animate-spin" />
|
||||
<div className="w-5 h-5 border-2 border-knot-500 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
) : query.trim().length >= 3 && users.length > 0 ? (
|
||||
users.map((u) => (
|
||||
@@ -263,7 +263,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
|
||||
onClick={() => handleSelectUser(u)}
|
||||
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl transition-colors ${
|
||||
isSelected(u.id)
|
||||
? 'bg-vortex-500/15 border border-vortex-500/30'
|
||||
? 'bg-knot-500/15 border border-knot-500/30'
|
||||
: 'hover:bg-surface-hover border border-transparent'
|
||||
}`}
|
||||
>
|
||||
@@ -271,7 +271,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
|
||||
{u.avatar ? (
|
||||
<img src={u.avatar} alt="" className="w-10 h-10 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-semibold text-sm">
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-semibold text-sm">
|
||||
{(u.displayName || u.username)?.[0]?.toUpperCase() || '?'}
|
||||
</div>
|
||||
)}
|
||||
@@ -288,7 +288,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
|
||||
{mode === 'group-select' && (
|
||||
<div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center flex-shrink-0 transition-colors ${
|
||||
isSelected(u.id)
|
||||
? 'bg-vortex-500 border-vortex-500'
|
||||
? 'bg-knot-500 border-knot-500'
|
||||
: 'border-zinc-600'
|
||||
}`}>
|
||||
{isSelected(u.id) && <Check size={12} className="text-white" />}
|
||||
@@ -313,7 +313,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
|
||||
onClick={() => handleSelectUser(u)}
|
||||
className={`w-full flex items-center gap-3 px-3 py-2.5 rounded-xl transition-colors ${
|
||||
isSelected(u.id)
|
||||
? 'bg-vortex-500/15 border border-vortex-500/30'
|
||||
? 'bg-knot-500/15 border border-knot-500/30'
|
||||
: 'hover:bg-surface-hover border border-transparent'
|
||||
}`}
|
||||
>
|
||||
@@ -321,7 +321,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
|
||||
{u.avatar ? (
|
||||
<img src={u.avatar} alt="" className="w-10 h-10 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-semibold text-sm">
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-semibold text-sm">
|
||||
{(u.displayName || u.username)?.[0]?.toUpperCase() || '?'}
|
||||
</div>
|
||||
)}
|
||||
@@ -338,7 +338,7 @@ export default function NewChatModal({ onClose }: NewChatModalProps) {
|
||||
{mode === 'group-select' && (
|
||||
<div className={`w-5 h-5 rounded-full border-2 flex items-center justify-center flex-shrink-0 transition-colors ${
|
||||
isSelected(u.id)
|
||||
? 'bg-vortex-500 border-vortex-500'
|
||||
? 'bg-knot-500 border-knot-500'
|
||||
: 'border-zinc-600'
|
||||
}`}>
|
||||
{isSelected(u.id) && <Check size={12} className="text-white" />}
|
||||
|
||||
@@ -247,7 +247,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
|
||||
|
||||
{ icon: Settings, label: t('settings'), onClick: () => changeView('settings') },
|
||||
{ divider: true },
|
||||
{ icon: Info, label: t('aboutApp'), subtitle: 'SelfHost Messenger v1.0', onClick: () => changeView('about') },
|
||||
{ icon: Info, label: t('aboutApp'), subtitle: 'Knot Messenger v1.0', onClick: () => changeView('about') },
|
||||
];
|
||||
|
||||
// Slide direction for animations
|
||||
@@ -264,8 +264,8 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
|
||||
{/* ── Premium header with avatar ── */}
|
||||
<div className="relative overflow-hidden flex-shrink-0">
|
||||
{/* Animated gradient backdrop */}
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-vortex-500/40 via-purple-600/25 to-transparent pointer-events-none" />
|
||||
<div className="absolute -top-20 -right-20 w-56 h-56 bg-vortex-500/15 rounded-full blur-[80px] pointer-events-none" />
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-knot-500/40 via-purple-600/25 to-transparent pointer-events-none" />
|
||||
<div className="absolute -top-20 -right-20 w-56 h-56 bg-knot-500/15 rounded-full blur-[80px] pointer-events-none" />
|
||||
<div className="absolute -bottom-10 -left-10 w-40 h-40 bg-purple-600/10 rounded-full blur-[60px] pointer-events-none" />
|
||||
|
||||
<div className="relative p-6 pb-5">
|
||||
@@ -295,8 +295,8 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
|
||||
{user?.displayName || user?.username}
|
||||
</h3>
|
||||
<div className="flex items-center gap-1.5 mt-1.5">
|
||||
<AtSign size={12} className="text-vortex-400" />
|
||||
<span className="text-sm font-medium text-vortex-100/70">{user?.username}</span>
|
||||
<AtSign size={12} className="text-knot-400" />
|
||||
<span className="text-sm font-medium text-knot-100/70">{user?.username}</span>
|
||||
</div>
|
||||
</div>
|
||||
{/* Bottom fade line */}
|
||||
@@ -314,15 +314,15 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
|
||||
onClick={item.onClick}
|
||||
className="group w-full flex items-center gap-3.5 px-4 py-3 rounded-2xl text-left transition-all duration-200 hover:bg-white/[0.06] active:scale-[0.98]"
|
||||
>
|
||||
<div className="w-9 h-9 rounded-xl bg-white/[0.06] group-hover:bg-vortex-500/15 flex items-center justify-center transition-all duration-200 border border-white/[0.04] group-hover:border-vortex-500/20">
|
||||
<Icon size={17} className="text-zinc-400 group-hover:text-vortex-400 transition-colors duration-200" />
|
||||
<div className="w-9 h-9 rounded-xl bg-white/[0.06] group-hover:bg-knot-500/15 flex items-center justify-center transition-all duration-200 border border-white/[0.04] group-hover:border-knot-500/20">
|
||||
<Icon size={17} className="text-zinc-400 group-hover:text-knot-400 transition-colors duration-200" />
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="text-[13.5px] font-medium text-zinc-200 group-hover:text-white transition-colors">{item.label}</p>
|
||||
{item.subtitle && <p className="text-[11px] text-zinc-500 mt-0.5">{item.subtitle}</p>}
|
||||
</div>
|
||||
{'badge' in item && item.badge ? (
|
||||
<span className="bg-gradient-to-r from-vortex-500 to-purple-600 text-white text-[11px] font-bold min-w-[22px] h-[22px] px-1.5 rounded-full flex items-center justify-center flex-shrink-0 shadow-[0_0_12px_rgba(168,85,247,0.4)]">
|
||||
<span className="bg-gradient-to-r from-knot-500 to-purple-600 text-white text-[11px] font-bold min-w-[22px] h-[22px] px-1.5 rounded-full flex items-center justify-center flex-shrink-0 shadow-[0_0_12px_rgba(168,85,247,0.4)]">
|
||||
{item.badge}
|
||||
</span>
|
||||
) : (
|
||||
@@ -380,23 +380,23 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
|
||||
<div className="space-y-1">
|
||||
<button
|
||||
onClick={() => setLang('ru')}
|
||||
className={`w-full flex items-center gap-4 px-3 py-3 rounded-xl transition-colors ${lang === 'ru' ? 'bg-vortex-500/15 ring-1 ring-vortex-500/30' : 'bg-surface-tertiary/50 hover:bg-surface-hover'}`}
|
||||
className={`w-full flex items-center gap-4 px-3 py-3 rounded-xl transition-colors ${lang === 'ru' ? 'bg-knot-500/15 ring-1 ring-knot-500/30' : 'bg-surface-tertiary/50 hover:bg-surface-hover'}`}
|
||||
>
|
||||
<span className="text-lg">🇷🇺</span>
|
||||
<div className="flex-1 min-w-0 text-left">
|
||||
<p className="text-sm text-zinc-200">Русский</p>
|
||||
</div>
|
||||
{lang === 'ru' && <Check size={16} className="text-vortex-400" />}
|
||||
{lang === 'ru' && <Check size={16} className="text-knot-400" />}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setLang('en')}
|
||||
className={`w-full flex items-center gap-4 px-3 py-3 rounded-xl transition-colors ${lang === 'en' ? 'bg-vortex-500/15 ring-1 ring-vortex-500/30' : 'bg-surface-tertiary/50 hover:bg-surface-hover'}`}
|
||||
className={`w-full flex items-center gap-4 px-3 py-3 rounded-xl transition-colors ${lang === 'en' ? 'bg-knot-500/15 ring-1 ring-knot-500/30' : 'bg-surface-tertiary/50 hover:bg-surface-hover'}`}
|
||||
>
|
||||
<span className="text-lg">🇬🇧</span>
|
||||
<div className="flex-1 min-w-0 text-left">
|
||||
<p className="text-sm text-zinc-200">English</p>
|
||||
</div>
|
||||
{lang === 'en' && <Check size={16} className="text-vortex-400" />}
|
||||
{lang === 'en' && <Check size={16} className="text-knot-400" />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -419,7 +419,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
|
||||
<p className="text-sm text-zinc-200">{t('hideStoryViews')}</p>
|
||||
<p className="text-[11px] text-zinc-500 mt-0.5">{t('hideStoryViewsDesc')}</p>
|
||||
</div>
|
||||
<div className={`w-10 h-6 rounded-full transition-colors flex items-center px-0.5 ${user?.hideStoryViews ? 'bg-vortex-500' : 'bg-zinc-600'}`}>
|
||||
<div className={`w-10 h-6 rounded-full transition-colors flex items-center px-0.5 ${user?.hideStoryViews ? 'bg-knot-500' : 'bg-zinc-600'}`}>
|
||||
<div className={`w-5 h-5 rounded-full bg-white shadow transition-transform ${user?.hideStoryViews ? 'translate-x-4' : 'translate-x-0'}`} />
|
||||
</div>
|
||||
</button>
|
||||
@@ -430,7 +430,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
|
||||
<div className="flex items-center gap-4 px-3 py-3 rounded-xl bg-surface-tertiary/50">
|
||||
<Info size={18} className="text-zinc-400" />
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm text-zinc-200">SelfHost Messenger</p>
|
||||
<p className="text-sm text-zinc-200">Knot Messenger</p>
|
||||
<p className="text-xs text-zinc-500">{t('version')} 1.0.0</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -603,7 +603,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
|
||||
{u.avatar ? (
|
||||
<img src={u.avatar} alt="" className="w-10 h-10 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
|
||||
{(u.displayName || u.username || '?')[0].toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
@@ -613,7 +613,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
|
||||
</div>
|
||||
<button
|
||||
onClick={() => handleSendFriendRequest(u.id)}
|
||||
className="p-2 rounded-lg bg-vortex-500/20 text-vortex-400 hover:bg-vortex-500/30 transition-colors"
|
||||
className="p-2 rounded-lg bg-knot-500/20 text-knot-400 hover:bg-knot-500/30 transition-colors"
|
||||
title={t('addFriend')}
|
||||
>
|
||||
<UserPlus size={16} />
|
||||
@@ -638,7 +638,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
|
||||
{req.user.avatar ? (
|
||||
<img src={req.user.avatar} alt="" className="w-10 h-10 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
|
||||
{(req.user.displayName || req.user.username || '?')[0].toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
@@ -683,7 +683,7 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
|
||||
{friend.avatar ? (
|
||||
<img src={friend.avatar} alt="" className="w-10 h-10 rounded-full object-cover" />
|
||||
) : (
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-vortex-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
|
||||
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-knot-500 to-purple-600 flex items-center justify-center text-white font-bold text-sm">
|
||||
{(friend.displayName || friend.username || '?')[0].toUpperCase()}
|
||||
</div>
|
||||
)}
|
||||
@@ -729,12 +729,12 @@ export default function SideMenu({ isOpen, onClose, onOpenProfile }: SideMenuPro
|
||||
<div className="absolute inset-0 rounded-2xl bg-gradient-to-br from-white/[0.05] to-transparent pointer-events-none" />
|
||||
<MessageSquare size={36} className="text-accent drop-shadow-md relative z-10" />
|
||||
</div>
|
||||
<h2 className="text-xl font-bold gradient-text mb-1">SelfHost Messenger</h2>
|
||||
<h2 className="text-xl font-bold gradient-text mb-1">Knot Messenger</h2>
|
||||
<p className="text-sm text-zinc-400 mb-6">{t('version')} 1.0.0</p>
|
||||
<div className="text-xs text-zinc-500 space-y-1">
|
||||
<p>{t('modernMessenger')}</p>
|
||||
<p>{t('onPrivacy')}</p>
|
||||
<p className="mt-4 text-zinc-600">© 2026 SelfHost Team</p>
|
||||
<p className="mt-4 text-zinc-600">© 2026 Knot Team</p>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -113,7 +113,7 @@ export default function Sidebar() {
|
||||
<div className="absolute inset-0 rounded-xl bg-gradient-to-br from-white/[0.05] to-transparent pointer-events-none" />
|
||||
<MessageSquare size={18} className="text-accent drop-shadow-md relative z-10" />
|
||||
</div>
|
||||
<h1 className="text-lg font-bold gradient-text truncate">SelfHost Messenger</h1>
|
||||
<h1 className="text-lg font-bold gradient-text truncate">Knot Messenger</h1>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setShowNewChat(true)}
|
||||
|
||||
@@ -703,13 +703,13 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps)
|
||||
<div className="flex border-b border-white/10">
|
||||
<button
|
||||
onClick={() => setMode('text')}
|
||||
className={`flex-1 py-2.5 text-sm font-medium transition-colors ${mode === 'text' ? 'text-vortex-400 border-b-2 border-vortex-400' : 'text-zinc-400'}`}
|
||||
className={`flex-1 py-2.5 text-sm font-medium transition-colors ${mode === 'text' ? 'text-knot-400 border-b-2 border-knot-400' : 'text-zinc-400'}`}
|
||||
>
|
||||
{t('textStory')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setMode('image')}
|
||||
className={`flex-1 py-2.5 text-sm font-medium transition-colors ${mode === 'image' ? 'text-vortex-400 border-b-2 border-vortex-400' : 'text-zinc-400'}`}
|
||||
className={`flex-1 py-2.5 text-sm font-medium transition-colors ${mode === 'image' ? 'text-knot-400 border-b-2 border-knot-400' : 'text-zinc-400'}`}
|
||||
>
|
||||
{t('mediaStory')}
|
||||
</button>
|
||||
@@ -740,7 +740,7 @@ export function CreateStoryModal({ onClose, onCreated }: CreateStoryModalProps)
|
||||
onChange={e => setText(e.target.value)}
|
||||
placeholder={t('typeYourStory')}
|
||||
maxLength={200}
|
||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-3 py-2 text-sm text-zinc-200 resize-none h-20 mb-3 focus:outline-none focus:border-vortex-500/50"
|
||||
className="w-full bg-white/5 border border-white/10 rounded-xl px-3 py-2 text-sm text-zinc-200 resize-none h-20 mb-3 focus:outline-none focus:border-knot-500/50"
|
||||
/>
|
||||
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
|
||||
@@ -5,12 +5,12 @@ export default function TypingIndicator() {
|
||||
const { t } = useLang();
|
||||
return (
|
||||
<div className="flex items-center gap-1 py-1">
|
||||
<span className="text-xs text-vortex-400 font-medium">{t('typingText')}</span>
|
||||
<span className="text-xs text-knot-400 font-medium">{t('typingText')}</span>
|
||||
<div className="flex gap-0.5">
|
||||
{[0, 1, 2].map((i) => (
|
||||
<motion.span
|
||||
key={i}
|
||||
className="w-1 h-1 rounded-full bg-vortex-400"
|
||||
className="w-1 h-1 rounded-full bg-knot-400"
|
||||
animate={{ opacity: [0.3, 1, 0.3] }}
|
||||
transition={{
|
||||
duration: 1,
|
||||
|
||||
@@ -144,7 +144,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
{groups.map((g, i) => (
|
||||
<div key={i}>
|
||||
<div className="sticky top-0 z-10 bg-black/60 backdrop-blur-md px-3 py-1.5 mb-1.5 shadow-sm border-y border-white/5">
|
||||
<span className="text-[10px] font-bold text-vortex-300 uppercase tracking-widest">{g.dateStr}</span>
|
||||
<span className="text-[10px] font-bold text-knot-300 uppercase tracking-widest">{g.dateStr}</span>
|
||||
</div>
|
||||
<div className={gridClass || "flex flex-col gap-0.5"}>
|
||||
{g.items.map(({item, idx}) => renderItem(item, idx))}
|
||||
@@ -375,7 +375,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
>
|
||||
{/* Шапка */}
|
||||
<div className="flex items-center justify-between p-5 border-b border-white/5 bg-white/5 relative overflow-hidden">
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-vortex-500/20 to-purple-500/10 pointer-events-none" />
|
||||
<div className="absolute inset-0 bg-gradient-to-r from-knot-500/20 to-purple-500/10 pointer-events-none" />
|
||||
<h2 className="text-xl font-bold tracking-tight text-white drop-shadow-sm relative z-10 flex-1">
|
||||
{(isSelf ? t('myProfile') : t('profileTitle')) as string}
|
||||
</h2>
|
||||
@@ -392,7 +392,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={isSaving}
|
||||
className="w-8 h-8 flex items-center justify-center rounded-full bg-vortex-500/20 text-vortex-400 hover:text-vortex-300 hover:bg-vortex-500/30 transition-all border border-vortex-500/30"
|
||||
className="w-8 h-8 flex items-center justify-center rounded-full bg-knot-500/20 text-knot-400 hover:text-knot-300 hover:bg-knot-500/30 transition-all border border-knot-500/30"
|
||||
>
|
||||
{isSaving ? <Loader2 size={16} className="animate-spin" /> : <Check size={16} />}
|
||||
</button>
|
||||
@@ -409,7 +409,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
|
||||
{isLoading ? (
|
||||
<div className="flex-1 flex items-center justify-center">
|
||||
<div className="w-8 h-8 border-2 border-vortex-500 border-t-transparent rounded-full animate-spin" />
|
||||
<div className="w-8 h-8 border-2 border-knot-500 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
) : profile ? (
|
||||
<div className="flex-1 flex flex-col overflow-hidden">
|
||||
@@ -417,7 +417,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
<div className="flex-shrink-0 overflow-y-auto max-h-[50%] custom-scrollbar">
|
||||
{/* Аватар */}
|
||||
<div className="flex flex-col items-center pt-8 pb-4 px-6 relative overflow-visible">
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[240px] h-[240px] bg-vortex-500/10 rounded-full blur-[80px] pointer-events-none" />
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-[240px] h-[240px] bg-knot-500/10 rounded-full blur-[80px] pointer-events-none" />
|
||||
|
||||
<div className="relative group">
|
||||
{/* Spinning gradient glow ring */}
|
||||
@@ -467,13 +467,13 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
{/* Имя */}
|
||||
{isEditing ? (
|
||||
<div className="mt-5 w-full max-w-[260px] relative">
|
||||
<div className="absolute -inset-0.5 bg-gradient-to-r from-vortex-500 to-purple-500 rounded-2xl opacity-50 blur-sm pointer-events-none" />
|
||||
<div className="absolute -inset-0.5 bg-gradient-to-r from-knot-500 to-purple-500 rounded-2xl opacity-50 blur-sm pointer-events-none" />
|
||||
<input
|
||||
type="text"
|
||||
value={displayName}
|
||||
onChange={(e) => setDisplayName(e.target.value)}
|
||||
placeholder={t('enterName')}
|
||||
className="relative text-lg font-bold text-center text-white bg-black/40 border border-white/20 outline-none px-4 py-2.5 w-full rounded-2xl transition-colors focus:bg-black/60 focus:border-vortex-400 placeholder-white/30 truncate"
|
||||
className="relative text-lg font-bold text-center text-white bg-black/40 border border-white/20 outline-none px-4 py-2.5 w-full rounded-2xl transition-colors focus:bg-black/60 focus:border-knot-400 placeholder-white/30 truncate"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
@@ -483,9 +483,9 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
)}
|
||||
|
||||
{/* Username (неизменяемый) */}
|
||||
<div className="flex items-center gap-1.5 mt-2.5 bg-vortex-500/10 hover:bg-vortex-500/20 transition-colors px-4 py-1.5 rounded-full border border-vortex-500/20 backdrop-blur-sm cursor-default">
|
||||
<AtSign size={14} className="text-vortex-400" />
|
||||
<span className="text-sm font-semibold text-vortex-100">{profile.username}</span>
|
||||
<div className="flex items-center gap-1.5 mt-2.5 bg-knot-500/10 hover:bg-knot-500/20 transition-colors px-4 py-1.5 rounded-full border border-knot-500/20 backdrop-blur-sm cursor-default">
|
||||
<AtSign size={14} className="text-knot-400" />
|
||||
<span className="text-sm font-semibold text-knot-100">{profile.username}</span>
|
||||
</div>
|
||||
|
||||
{/* Онлайн статус */}
|
||||
@@ -510,7 +510,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
<button
|
||||
onClick={handleSendFriendRequest}
|
||||
disabled={friendLoading}
|
||||
className="flex items-center gap-2 px-5 py-2.5 rounded-full bg-vortex-500/20 border border-vortex-500/30 text-vortex-300 hover:bg-vortex-500/30 transition-all text-sm font-medium"
|
||||
className="flex items-center gap-2 px-5 py-2.5 rounded-full bg-knot-500/20 border border-knot-500/30 text-knot-300 hover:bg-knot-500/30 transition-all text-sm font-medium"
|
||||
>
|
||||
{friendLoading ? <Loader2 size={16} className="animate-spin" /> : <UserPlus size={16} />}
|
||||
{t('addFriend')}
|
||||
@@ -602,10 +602,10 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
{/* О себе */}
|
||||
<div className="bg-black/20 backdrop-blur-xl border border-white/5 rounded-2xl p-4 transition-all hover:bg-black/30 hover:border-white/10 group">
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<div className="w-6 h-6 rounded-full bg-vortex-500/20 flex items-center justify-center border border-vortex-500/30">
|
||||
<Edit3 size={12} className="text-vortex-400" />
|
||||
<div className="w-6 h-6 rounded-full bg-knot-500/20 flex items-center justify-center border border-knot-500/30">
|
||||
<Edit3 size={12} className="text-knot-400" />
|
||||
</div>
|
||||
<label className="text-xs font-semibold text-vortex-200/50 uppercase tracking-widest">
|
||||
<label className="text-xs font-semibold text-knot-200/50 uppercase tracking-widest">
|
||||
{t('aboutMe')}
|
||||
</label>
|
||||
</div>
|
||||
@@ -614,7 +614,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
value={bio}
|
||||
onChange={(e) => setBio(e.target.value)}
|
||||
rows={3}
|
||||
className="w-full rounded-xl bg-black/40 text-sm text-white placeholder-white/30 p-3 border border-white/10 focus:border-vortex-500 transition-colors resize-none outline-none leading-relaxed"
|
||||
className="w-full rounded-xl bg-black/40 text-sm text-white placeholder-white/30 p-3 border border-white/10 focus:border-knot-500 transition-colors resize-none outline-none leading-relaxed"
|
||||
placeholder={t('tellAboutYourself')}
|
||||
/>
|
||||
) : (
|
||||
@@ -662,7 +662,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
<Check size={12} className="text-emerald-400" />
|
||||
</div>
|
||||
<label className="text-xs font-semibold text-emerald-200/50 uppercase tracking-widest">
|
||||
{t('onVortexSince')}
|
||||
{t('onKnotSince')}
|
||||
</label>
|
||||
</div>
|
||||
<p className="text-sm text-zinc-200 pl-1">
|
||||
@@ -715,7 +715,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
onClick={() => setActiveTab(tab.key)}
|
||||
className={`flex-1 flex flex-col items-center justify-center gap-1 py-1 text-[10px] font-bold uppercase tracking-widest transition-all ${
|
||||
activeTab === tab.key
|
||||
? 'bg-white/5 text-vortex-400'
|
||||
? 'bg-white/5 text-knot-400'
|
||||
: 'text-zinc-500 hover:text-white/70'
|
||||
}`}
|
||||
>
|
||||
@@ -832,8 +832,8 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
download={m.filename || 'file'}
|
||||
className="flex items-center gap-3 px-4 py-3 hover:bg-white/5 transition-colors"
|
||||
>
|
||||
<div className="w-10 h-10 rounded-xl bg-vortex-500/20 flex items-center justify-center flex-shrink-0 border border-vortex-500/30 group-hover/file:scale-105 transition-transform">
|
||||
<FileText size={18} className="text-vortex-400" />
|
||||
<div className="w-10 h-10 rounded-xl bg-knot-500/20 flex items-center justify-center flex-shrink-0 border border-knot-500/30 group-hover/file:scale-105 transition-transform">
|
||||
<FileText size={18} className="text-knot-400" />
|
||||
</div>
|
||||
<div className="flex-1 min-w-0">
|
||||
<p className="text-sm text-white truncate">{m.filename || 'file'}</p>
|
||||
@@ -872,7 +872,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
href={link}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 text-sm text-vortex-400 hover:text-vortex-300 transition-colors truncate"
|
||||
className="flex items-center gap-2 text-sm text-knot-400 hover:text-knot-300 transition-colors truncate"
|
||||
>
|
||||
<ExternalLink size={14} className="flex-shrink-0" />
|
||||
<span className="truncate">{link}</span>
|
||||
@@ -883,7 +883,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
)}
|
||||
<button
|
||||
onClick={() => onGoToMessage?.(msg.id)}
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 px-3 py-1.5 rounded-lg bg-black/40 hover:bg-vortex-500/20 text-zinc-300 hover:text-white text-[11px] font-medium opacity-0 group-hover:opacity-100 transition-all shadow-md z-10"
|
||||
className="absolute right-4 top-1/2 -translate-y-1/2 px-3 py-1.5 rounded-lg bg-black/40 hover:bg-knot-500/20 text-zinc-300 hover:text-white text-[11px] font-medium opacity-0 group-hover:opacity-100 transition-all shadow-md z-10"
|
||||
>
|
||||
{t('showInChat')}
|
||||
</button>
|
||||
@@ -994,7 +994,7 @@ export default function UserProfile({ userId, chatId, onClose, onGoToMessage, is
|
||||
step={0.1}
|
||||
aria-labelledby="Zoom"
|
||||
onChange={(e) => setZoom(Number(e.target.value))}
|
||||
className="flex-1 accent-vortex-500"
|
||||
className="flex-1 accent-knot-500"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
+11
-11
@@ -3,17 +3,17 @@
|
||||
@theme {
|
||||
--font-sans: 'Inter', system-ui, -apple-system, sans-serif;
|
||||
|
||||
--color-vortex-50: #eef2ff;
|
||||
--color-vortex-100: #e0e7ff;
|
||||
--color-vortex-200: #c7d2fe;
|
||||
--color-vortex-300: #a5b4fc;
|
||||
--color-vortex-400: #818cf8;
|
||||
--color-vortex-500: #6366f1;
|
||||
--color-vortex-600: #4f46e5;
|
||||
--color-vortex-700: #4338ca;
|
||||
--color-vortex-800: #3730a3;
|
||||
--color-vortex-900: #312e81;
|
||||
--color-vortex-950: #1e1b4b;
|
||||
--color-knot-50: #eef2ff;
|
||||
--color-knot-100: #e0e7ff;
|
||||
--color-knot-200: #c7d2fe;
|
||||
--color-knot-300: #a5b4fc;
|
||||
--color-knot-400: #818cf8;
|
||||
--color-knot-500: #6366f1;
|
||||
--color-knot-600: #4f46e5;
|
||||
--color-knot-700: #4338ca;
|
||||
--color-knot-800: #3730a3;
|
||||
--color-knot-900: #312e81;
|
||||
--color-knot-950: #1e1b4b;
|
||||
|
||||
--color-surface: #09090b;
|
||||
--color-surface-secondary: #111113;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { create } from 'zustand';
|
||||
|
||||
export type Lang = 'ru' | 'en';
|
||||
|
||||
const LANG_KEY = 'vortex_language';
|
||||
const LANG_KEY = 'knot_language';
|
||||
|
||||
const translations = {
|
||||
ru: {
|
||||
@@ -174,7 +174,7 @@ const translations = {
|
||||
removeAvatar: 'Удалить аватар',
|
||||
namePlaceholder: 'Имя',
|
||||
notSpecified: 'Не указано',
|
||||
onVortexSince: 'На SelfHost с',
|
||||
onKnotSince: 'На Knot с',
|
||||
cancel: 'Отмена',
|
||||
confirm: 'Подтвердить',
|
||||
save: 'Сохранить',
|
||||
@@ -305,7 +305,7 @@ const translations = {
|
||||
version: 'Version',
|
||||
modernMessenger: 'A modern messenger focused',
|
||||
onPrivacy: 'on privacy and convenience',
|
||||
modernMessengerShort: 'SelfHost Messenger',
|
||||
modernMessengerShort: 'Knot Messenger',
|
||||
searchChats: 'Search chats...',
|
||||
chat: 'Chat',
|
||||
group: 'Group',
|
||||
@@ -442,7 +442,7 @@ const translations = {
|
||||
removeAvatar: 'Remove avatar',
|
||||
namePlaceholder: 'Name',
|
||||
notSpecified: 'Not specified',
|
||||
onVortexSince: 'On SelfHost since',
|
||||
onKnotSince: 'On Knot since',
|
||||
cancel: 'Cancel',
|
||||
confirm: 'Confirm',
|
||||
save: 'Save',
|
||||
|
||||
@@ -48,7 +48,7 @@ export function playNotificationSound() {
|
||||
}
|
||||
|
||||
// Muted chats stored in localStorage
|
||||
const MUTED_KEY = 'vortex_muted_chats';
|
||||
const MUTED_KEY = 'knot_muted_chats';
|
||||
|
||||
export function getMutedChats(): Set<string> {
|
||||
try {
|
||||
|
||||
@@ -70,7 +70,7 @@ export default function AuthPage() {
|
||||
<div className="absolute inset-0 rounded-[2rem] bg-gradient-to-br from-white/[0.05] to-transparent pointer-events-none" />
|
||||
<MessageSquare className="w-10 h-10 text-accent drop-shadow-md relative z-10" />
|
||||
</motion.div>
|
||||
<h1 className="text-3xl font-bold gradient-text mt-6">SelfHost Messenger</h1>
|
||||
<h1 className="text-3xl font-bold gradient-text mt-6">Knot Messenger</h1>
|
||||
<p className="text-zinc-500 text-sm mt-2 tracking-wide uppercase">
|
||||
{isLogin ? t('login') : t('register')}
|
||||
</p>
|
||||
@@ -127,7 +127,7 @@ export default function AuthPage() {
|
||||
value={displayName}
|
||||
onChange={(e) => setDisplayName(e.target.value)}
|
||||
placeholder={t('displayNamePlaceholder')}
|
||||
className="w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white placeholder-zinc-600 focus:border-vortex-500/50 focus:ring-1 focus:ring-vortex-500/25 transition-all outline-hidden"
|
||||
className="w-full px-4 py-3 rounded-xl bg-white/5 border border-white/10 text-white placeholder-zinc-600 focus:border-knot-500/50 focus:ring-1 focus:ring-knot-500/25 transition-all outline-hidden"
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
@@ -16,7 +16,7 @@ interface AuthState {
|
||||
}
|
||||
|
||||
export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
token: localStorage.getItem('vortex_token'),
|
||||
token: localStorage.getItem('knot_token'),
|
||||
user: null,
|
||||
isLoading: true,
|
||||
error: null,
|
||||
@@ -25,7 +25,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
try {
|
||||
set({ error: null, isLoading: true });
|
||||
const { token, user } = await api.login(username, password);
|
||||
localStorage.setItem('vortex_token', token);
|
||||
localStorage.setItem('knot_token', token);
|
||||
api.setToken(token);
|
||||
connectSocket(token);
|
||||
set({ token, user, isLoading: false });
|
||||
@@ -40,7 +40,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
try {
|
||||
set({ error: null, isLoading: true });
|
||||
const { token, user } = await api.register(username, displayName, password, bio);
|
||||
localStorage.setItem('vortex_token', token);
|
||||
localStorage.setItem('knot_token', token);
|
||||
api.setToken(token);
|
||||
connectSocket(token);
|
||||
set({ token, user, isLoading: false });
|
||||
@@ -52,7 +52,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
},
|
||||
|
||||
logout: () => {
|
||||
localStorage.removeItem('vortex_token');
|
||||
localStorage.removeItem('knot_token');
|
||||
api.setToken(null);
|
||||
disconnectSocket();
|
||||
set({ token: null, user: null });
|
||||
@@ -87,7 +87,7 @@ export const useAuthStore = create<AuthState>((set, get) => ({
|
||||
}
|
||||
}
|
||||
console.warn('checkAuth failed:', lastError);
|
||||
localStorage.removeItem('vortex_token');
|
||||
localStorage.removeItem('knot_token');
|
||||
set({ token: null, user: null, isLoading: false });
|
||||
},
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
isLoadingChats: false,
|
||||
isLoadingMessages: false,
|
||||
searchQuery: '',
|
||||
drafts: JSON.parse(localStorage.getItem('vortex_drafts') || '{}'),
|
||||
drafts: JSON.parse(localStorage.getItem('knot_drafts') || '{}'),
|
||||
|
||||
setActiveChat: (chatId) => set((state) => ({
|
||||
activeChat: chatId,
|
||||
@@ -75,7 +75,7 @@ export const useChatStore = create<ChatState>((set, get) => ({
|
||||
} else {
|
||||
delete drafts[chatId];
|
||||
}
|
||||
localStorage.setItem('vortex_drafts', JSON.stringify(drafts));
|
||||
localStorage.setItem('knot_drafts', JSON.stringify(drafts));
|
||||
return { drafts };
|
||||
});
|
||||
},
|
||||
|
||||
@@ -15,7 +15,7 @@ export const useThemeStore = create<ThemeState>()(
|
||||
setChatTheme: (theme) => set({ chatTheme: theme }),
|
||||
}),
|
||||
{
|
||||
name: 'vortex-theme-storage',
|
||||
name: 'knot-theme-storage',
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/avatar.tsx","./src/components/callmodal.tsx","./src/components/chatlistitem.tsx","./src/components/chatview.tsx","./src/components/confirmmodal.tsx","./src/components/datepicker.tsx","./src/components/emojipicker.tsx","./src/components/forwardmodal.tsx","./src/components/groupcallmodal.tsx","./src/components/groupsettings.tsx","./src/components/imagelightbox.tsx","./src/components/linkpreview.tsx","./src/components/messagebubble.tsx","./src/components/messageinput.tsx","./src/components/newchatmodal.tsx","./src/components/notificationprovider.tsx","./src/components/sidemenu.tsx","./src/components/sidebar.tsx","./src/components/storyviewer.tsx","./src/components/typingindicator.tsx","./src/components/userprofile.tsx","./src/lib/api.ts","./src/lib/hooks.ts","./src/lib/i18n.ts","./src/lib/imagecrop.ts","./src/lib/socket.ts","./src/lib/sounds.ts","./src/lib/types.ts","./src/lib/utils.ts","./src/pages/authpage.tsx","./src/pages/chatpage.tsx","./src/stores/authstore.ts","./src/stores/chatstore.ts","./src/stores/notificationstore.ts","./src/stores/themestore.ts","./src/stores/usestorystore.ts"],"version":"5.9.3"}
|
||||
Reference in New Issue
Block a user