Spaces:
Running
Running
بالنسبه الدردشه يمكنك ازاله قسم الدردشه العاديه والدردشه الجماعيه واضافه
Browse files- src/app/page.tsx +4 -2
- src/components/community/community-hub.tsx +315 -0
- src/components/header.tsx +3 -4
- src/lib/translations.ts +26 -28
src/app/page.tsx
CHANGED
|
@@ -1,11 +1,12 @@
|
|
| 1 |
|
| 2 |
'use client';
|
| 3 |
-
import {
|
| 4 |
import { useEffect } from 'react';
|
| 5 |
import { Loader2 } from 'lucide-react';
|
| 6 |
import { useAuth } from '@/contexts/auth-context';
|
| 7 |
import { useRouter } from 'next/navigation';
|
| 8 |
import { useLanguage } from '@/contexts/language-context';
|
|
|
|
| 9 |
|
| 10 |
export default function Home() {
|
| 11 |
const { user, loading: authLoading } = useAuth();
|
|
@@ -29,8 +30,9 @@ export default function Home() {
|
|
| 29 |
|
| 30 |
return (
|
| 31 |
<div className="relative h-screen w-full flex flex-col pt-16">
|
|
|
|
| 32 |
<main className="flex-1 overflow-hidden">
|
| 33 |
-
<
|
| 34 |
</main>
|
| 35 |
</div>
|
| 36 |
);
|
|
|
|
| 1 |
|
| 2 |
'use client';
|
| 3 |
+
import { CommunityHub } from '@/components/community/community-hub';
|
| 4 |
import { useEffect } from 'react';
|
| 5 |
import { Loader2 } from 'lucide-react';
|
| 6 |
import { useAuth } from '@/contexts/auth-context';
|
| 7 |
import { useRouter } from 'next/navigation';
|
| 8 |
import { useLanguage } from '@/contexts/language-context';
|
| 9 |
+
import { Header } from '@/components/header';
|
| 10 |
|
| 11 |
export default function Home() {
|
| 12 |
const { user, loading: authLoading } = useAuth();
|
|
|
|
| 30 |
|
| 31 |
return (
|
| 32 |
<div className="relative h-screen w-full flex flex-col pt-16">
|
| 33 |
+
<Header title={t.communityTitle} />
|
| 34 |
<main className="flex-1 overflow-hidden">
|
| 35 |
+
<CommunityHub />
|
| 36 |
</main>
|
| 37 |
</div>
|
| 38 |
);
|
src/components/community/community-hub.tsx
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
'use client';
|
| 3 |
+
|
| 4 |
+
import React, { useState, useEffect, useRef } from 'react';
|
| 5 |
+
import { useAuth } from '@/contexts/auth-context';
|
| 6 |
+
import { useLanguage } from '@/contexts/language-context';
|
| 7 |
+
import { database } from '@/firebase/client';
|
| 8 |
+
import { ref, onValue, push, serverTimestamp, set, query, orderByChild } from 'firebase/database';
|
| 9 |
+
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
| 10 |
+
import { Input } from '@/components/ui/input';
|
| 11 |
+
import { Button } from '@/components/ui/button';
|
| 12 |
+
import { Search, Send, Bot, User as UserIcon, Loader2, Sparkles, ArrowLeft } from 'lucide-react';
|
| 13 |
+
import { cn } from '@/lib/utils';
|
| 14 |
+
import { getChatResponse } from '@/ai/flows/ai-chat-response';
|
| 15 |
+
import { checkConsumption, recordConsumption } from '@/lib/consumption';
|
| 16 |
+
import { useToast } from '@/hooks/use-toast';
|
| 17 |
+
import { maleNames, femaleNames } from '@/lib/ai-world-names';
|
| 18 |
+
|
| 19 |
+
interface CommunityUser {
|
| 20 |
+
id: string;
|
| 21 |
+
name: string;
|
| 22 |
+
avatarUrl: string;
|
| 23 |
+
isAi: boolean;
|
| 24 |
+
lastMessage?: string;
|
| 25 |
+
lastMessageTime?: number;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
interface Message {
|
| 29 |
+
id: string;
|
| 30 |
+
senderId: string;
|
| 31 |
+
text: string;
|
| 32 |
+
timestamp: number;
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
export const CommunityHub = () => {
|
| 36 |
+
const { user, userData } = useAuth();
|
| 37 |
+
const { t, lang, aiEngine } = useLanguage();
|
| 38 |
+
const { toast } = useToast();
|
| 39 |
+
const [activeChat, setActiveChat] = useState<CommunityUser | null>(null);
|
| 40 |
+
const [users, setUsers] = useState<CommunityUser[]>([]);
|
| 41 |
+
const [messages, setMessages] = useState<Message[]>([]);
|
| 42 |
+
const [inputText, setInputText] = useState('');
|
| 43 |
+
const [searchQuery, setSearchQuery] = useState('');
|
| 44 |
+
const [isLoading, setIsLoading] = useState(false);
|
| 45 |
+
const scrollRef = useRef<HTMLDivElement>(null);
|
| 46 |
+
|
| 47 |
+
// توليد قائمة مستخدمي AI ثابتة لهذا المستخدم
|
| 48 |
+
const aiPersonas: CommunityUser[] = [
|
| 49 |
+
{ id: 'ai-zeina', name: lang === 'ar' ? 'زينة' : 'Zeina', avatarUrl: 'https://api.dicebear.com/7.x/avataaars/svg?seed=Zeina', isAi: true },
|
| 50 |
+
{ id: 'ai-omar', name: maleNames[0], avatarUrl: `https://api.dicebear.com/7.x/avataaars/svg?seed=${maleNames[0]}`, isAi: true },
|
| 51 |
+
{ id: 'ai-sara', name: femaleNames[0], avatarUrl: `https://api.dicebear.com/7.x/avataaars/svg?seed=${femaleNames[0]}`, isAi: true },
|
| 52 |
+
];
|
| 53 |
+
|
| 54 |
+
useEffect(() => {
|
| 55 |
+
// جلب المستخدمين الحقيقيين من RTDB
|
| 56 |
+
const usersRef = ref(database, 'users');
|
| 57 |
+
const unsubscribe = onValue(usersRef, (snapshot) => {
|
| 58 |
+
const data = snapshot.val();
|
| 59 |
+
if (data) {
|
| 60 |
+
const realUsers = Object.entries(data)
|
| 61 |
+
.filter(([uid]) => uid !== user?.uid)
|
| 62 |
+
.map(([uid, val]: [string, any]) => ({
|
| 63 |
+
id: uid,
|
| 64 |
+
name: val.displayName || 'Anonymous',
|
| 65 |
+
avatarUrl: val.photoURL || `https://api.dicebear.com/7.x/avataaars/svg?seed=${uid}`,
|
| 66 |
+
isAi: false
|
| 67 |
+
}));
|
| 68 |
+
setUsers([...aiPersonas, ...realUsers]);
|
| 69 |
+
} else {
|
| 70 |
+
setUsers(aiPersonas);
|
| 71 |
+
}
|
| 72 |
+
});
|
| 73 |
+
|
| 74 |
+
return () => unsubscribe();
|
| 75 |
+
}, [user, lang]);
|
| 76 |
+
|
| 77 |
+
useEffect(() => {
|
| 78 |
+
if (!activeChat || !user) return;
|
| 79 |
+
|
| 80 |
+
// جلب الرسائل للمحادثة النشطة
|
| 81 |
+
const chatId = [user.uid, activeChat.id].sort().join('_');
|
| 82 |
+
const messagesRef = ref(database, `chats/${chatId}/messages`);
|
| 83 |
+
const q = query(messagesRef);
|
| 84 |
+
|
| 85 |
+
const unsubscribe = onValue(q, (snapshot) => {
|
| 86 |
+
const data = snapshot.val();
|
| 87 |
+
if (data) {
|
| 88 |
+
const msgs = Object.entries(data).map(([id, val]: [string, any]) => ({
|
| 89 |
+
id,
|
| 90 |
+
...val
|
| 91 |
+
}));
|
| 92 |
+
setMessages(msgs.sort((a, b) => a.timestamp - b.timestamp));
|
| 93 |
+
} else {
|
| 94 |
+
setMessages([]);
|
| 95 |
+
}
|
| 96 |
+
});
|
| 97 |
+
|
| 98 |
+
return () => unsubscribe();
|
| 99 |
+
}, [activeChat, user]);
|
| 100 |
+
|
| 101 |
+
useEffect(() => {
|
| 102 |
+
if (scrollRef.current) {
|
| 103 |
+
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
|
| 104 |
+
}
|
| 105 |
+
}, [messages]);
|
| 106 |
+
|
| 107 |
+
const handleSendMessage = async () => {
|
| 108 |
+
if (!inputText.trim() || !activeChat || !user) return;
|
| 109 |
+
|
| 110 |
+
const messageText = inputText.trim();
|
| 111 |
+
setInputText('');
|
| 112 |
+
|
| 113 |
+
const chatId = [user.uid, activeChat.id].sort().join('_');
|
| 114 |
+
const messagesRef = ref(database, `chats/${chatId}/messages`);
|
| 115 |
+
const newMessageRef = push(messagesRef);
|
| 116 |
+
|
| 117 |
+
const msgData = {
|
| 118 |
+
senderId: user.uid,
|
| 119 |
+
text: messageText,
|
| 120 |
+
timestamp: Date.now()
|
| 121 |
+
};
|
| 122 |
+
|
| 123 |
+
set(newMessageRef, msgData);
|
| 124 |
+
|
| 125 |
+
// إذا كان الطرف الآخر AI، قم بتوليد رد
|
| 126 |
+
if (activeChat.isAi) {
|
| 127 |
+
setIsLoading(true);
|
| 128 |
+
const consumption = checkConsumption();
|
| 129 |
+
if (!consumption.allowed) {
|
| 130 |
+
toast({ title: t.dailyLimitReached, variant: "destructive" });
|
| 131 |
+
setIsLoading(false);
|
| 132 |
+
return;
|
| 133 |
+
}
|
| 134 |
+
|
| 135 |
+
try {
|
| 136 |
+
const chatHistory = messages.slice(-5).map(m => `${m.senderId === user.uid ? 'User' : 'Assistant'}: ${m.text}`);
|
| 137 |
+
chatHistory.push(`User: ${messageText}`);
|
| 138 |
+
|
| 139 |
+
const aiResponse = await getChatResponse({ chatHistory, aiEngine });
|
| 140 |
+
recordConsumption();
|
| 141 |
+
|
| 142 |
+
const aiMsgRef = push(messagesRef);
|
| 143 |
+
set(aiMsgRef, {
|
| 144 |
+
senderId: activeChat.id,
|
| 145 |
+
text: aiResponse.response,
|
| 146 |
+
timestamp: Date.now()
|
| 147 |
+
});
|
| 148 |
+
} catch (error) {
|
| 149 |
+
console.error("AI Error:", error);
|
| 150 |
+
toast({ title: t.errorAI, variant: "destructive" });
|
| 151 |
+
} finally {
|
| 152 |
+
setIsLoading(false);
|
| 153 |
+
}
|
| 154 |
+
}
|
| 155 |
+
};
|
| 156 |
+
|
| 157 |
+
const filteredUsers = users.filter(u =>
|
| 158 |
+
u.name.toLowerCase().includes(searchQuery.toLowerCase())
|
| 159 |
+
);
|
| 160 |
+
|
| 161 |
+
return (
|
| 162 |
+
<div className="flex h-full w-full bg-background overflow-hidden">
|
| 163 |
+
{/* Sidebar - User List */}
|
| 164 |
+
<div className={cn(
|
| 165 |
+
"w-full md:w-80 border-l bg-muted/10 flex flex-col shrink-0",
|
| 166 |
+
activeChat && "hidden md:flex"
|
| 167 |
+
)}>
|
| 168 |
+
<div className="p-4 border-b">
|
| 169 |
+
<div className="relative">
|
| 170 |
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
|
| 171 |
+
<Input
|
| 172 |
+
placeholder={t.search}
|
| 173 |
+
className="pl-10 rounded-xl bg-muted/50 border-none"
|
| 174 |
+
value={searchQuery}
|
| 175 |
+
onChange={(e) => setSearchQuery(e.target.value)}
|
| 176 |
+
/>
|
| 177 |
+
</div>
|
| 178 |
+
</div>
|
| 179 |
+
<div className="flex-1 overflow-y-auto p-2 space-y-1 scrollbar-hide">
|
| 180 |
+
{filteredUsers.length > 0 ? filteredUsers.map((u) => (
|
| 181 |
+
<button
|
| 182 |
+
key={u.id}
|
| 183 |
+
onClick={() => setActiveChat(u)}
|
| 184 |
+
className={cn(
|
| 185 |
+
"w-full p-3 rounded-2xl flex items-center gap-3 transition-all hover:bg-primary/5 group",
|
| 186 |
+
activeChat?.id === u.id && "bg-primary/10"
|
| 187 |
+
)}
|
| 188 |
+
>
|
| 189 |
+
<div className="relative">
|
| 190 |
+
<Avatar className="h-12 w-12 border-2 border-primary/10">
|
| 191 |
+
<AvatarImage src={u.avatarUrl} />
|
| 192 |
+
<AvatarFallback>{u.name.charAt(0)}</AvatarFallback>
|
| 193 |
+
</Avatar>
|
| 194 |
+
{u.isAi && <div className="absolute -bottom-1 -right-1 bg-purple-500 rounded-full p-0.5 border-2 border-background"><Bot className="h-3 w-3 text-white" /></div>}
|
| 195 |
+
</div>
|
| 196 |
+
<div className="flex-1 text-right">
|
| 197 |
+
<div className="flex justify-between items-center">
|
| 198 |
+
<p className="font-bold text-sm">{u.name}</p>
|
| 199 |
+
<span className="text-[10px] text-muted-foreground">{u.isAi ? t.aiLabel : t.humanLabel}</span>
|
| 200 |
+
</div>
|
| 201 |
+
<p className="text-xs text-muted-foreground truncate max-w-[140px] mt-0.5">
|
| 202 |
+
{u.isAi ? t.online : t.startChat}
|
| 203 |
+
</p>
|
| 204 |
+
</div>
|
| 205 |
+
</button>
|
| 206 |
+
)) : (
|
| 207 |
+
<div className="text-center py-10">
|
| 208 |
+
<p className="text-sm text-muted-foreground font-bold">{t.noUsers}</p>
|
| 209 |
+
</div>
|
| 210 |
+
)}
|
| 211 |
+
</div>
|
| 212 |
+
</div>
|
| 213 |
+
|
| 214 |
+
{/* Main Chat Area */}
|
| 215 |
+
<div className={cn(
|
| 216 |
+
"flex-1 flex flex-col min-w-0 bg-background/50",
|
| 217 |
+
!activeChat && "hidden md:flex"
|
| 218 |
+
)}>
|
| 219 |
+
{activeChat ? (
|
| 220 |
+
<>
|
| 221 |
+
<div className="p-4 border-b flex items-center justify-between radiant-glass">
|
| 222 |
+
<div className="flex items-center gap-3">
|
| 223 |
+
<Button variant="ghost" size="icon" className="md:hidden rounded-full" onClick={() => setActiveChat(null)}>
|
| 224 |
+
<ArrowLeft className="h-5 w-5" />
|
| 225 |
+
</Button>
|
| 226 |
+
<Avatar className="h-10 w-10">
|
| 227 |
+
<AvatarImage src={activeChat.avatarUrl} />
|
| 228 |
+
<AvatarFallback>{activeChat.name.charAt(0)}</AvatarFallback>
|
| 229 |
+
</Avatar>
|
| 230 |
+
<div>
|
| 231 |
+
<h2 className="font-bold text-base leading-tight">{activeChat.name}</h2>
|
| 232 |
+
<p className="text-[10px] text-primary font-black uppercase tracking-widest">{activeChat.isAi ? t.aiLabel : t.online}</p>
|
| 233 |
+
</div>
|
| 234 |
+
</div>
|
| 235 |
+
</div>
|
| 236 |
+
|
| 237 |
+
<div ref={scrollRef} className="flex-1 overflow-y-auto p-4 md:p-8 space-y-6 scrollbar-hide">
|
| 238 |
+
{messages.map((m) => (
|
| 239 |
+
<div key={m.id} className={cn(
|
| 240 |
+
"flex items-start gap-3 animate-fade-in-slide-up",
|
| 241 |
+
m.senderId === user?.uid ? "flex-row-reverse" : "flex-row"
|
| 242 |
+
)}>
|
| 243 |
+
<Avatar className="h-8 w-8 shrink-0">
|
| 244 |
+
<AvatarImage src={m.senderId === user?.uid ? user?.photoURL! : activeChat.avatarUrl} />
|
| 245 |
+
<AvatarFallback>{m.senderId === user?.uid ? 'U' : 'A'}</AvatarFallback>
|
| 246 |
+
</Avatar>
|
| 247 |
+
<div className={cn(
|
| 248 |
+
"max-w-[75%] px-4 py-3 rounded-[2rem]",
|
| 249 |
+
m.senderId === user?.uid
|
| 250 |
+
? "bg-primary text-primary-foreground rounded-tr-none"
|
| 251 |
+
: "bg-muted rounded-tl-none"
|
| 252 |
+
)}>
|
| 253 |
+
<p className="text-sm font-medium leading-relaxed">{m.text}</p>
|
| 254 |
+
</div>
|
| 255 |
+
</div>
|
| 256 |
+
))}
|
| 257 |
+
{isLoading && (
|
| 258 |
+
<div className="flex items-start gap-3 animate-pulse">
|
| 259 |
+
<Avatar className="h-8 w-8"><AvatarFallback><Loader2 className="animate-spin h-4 w-4" /></AvatarFallback></Avatar>
|
| 260 |
+
<div className="bg-muted px-4 py-2 rounded-2xl rounded-tl-none">
|
| 261 |
+
<p className="text-xs font-bold text-muted-foreground">{t.protoThinking}</p>
|
| 262 |
+
</div>
|
| 263 |
+
</div>
|
| 264 |
+
)}
|
| 265 |
+
</div>
|
| 266 |
+
|
| 267 |
+
<div className="p-4 md:p-8 bg-gradient-to-t from-background via-background/80 to-transparent">
|
| 268 |
+
<div className="max-w-4xl mx-auto relative group">
|
| 269 |
+
<div className="absolute -inset-1 bg-primary/20 rounded-[2.5rem] blur opacity-0 group-focus-within:opacity-100 transition duration-500"></div>
|
| 270 |
+
<div className="relative flex items-center gap-2 bg-card border-2 border-border/50 rounded-[2.5rem] p-2 pr-4 shadow-xl focus-within:border-primary/50 transition-all">
|
| 271 |
+
<Input
|
| 272 |
+
value={inputText}
|
| 273 |
+
onChange={(e) => setInputText(e.target.value)}
|
| 274 |
+
placeholder={t.typeMessage}
|
| 275 |
+
onKeyDown={(e) => e.key === 'Enter' && handleSendMessage()}
|
| 276 |
+
className="flex-1 h-12 border-none bg-transparent focus-visible:ring-0 text-base font-medium"
|
| 277 |
+
disabled={isLoading}
|
| 278 |
+
/>
|
| 279 |
+
<Button
|
| 280 |
+
onClick={handleSendMessage}
|
| 281 |
+
disabled={!inputText.trim() || isLoading}
|
| 282 |
+
size="icon"
|
| 283 |
+
className="h-10 w-10 rounded-full shrink-0"
|
| 284 |
+
>
|
| 285 |
+
<Send className="h-5 w-5" />
|
| 286 |
+
</Button>
|
| 287 |
+
</div>
|
| 288 |
+
</div>
|
| 289 |
+
</div>
|
| 290 |
+
</>
|
| 291 |
+
) : (
|
| 292 |
+
<div className="flex-1 flex flex-col items-center justify-center text-center p-8 space-y-6">
|
| 293 |
+
<div className="w-24 h-24 bg-primary/10 rounded-[2.5rem] flex items-center justify-center animate-float">
|
| 294 |
+
<Sparkles className="h-12 w-12 text-primary" />
|
| 295 |
+
</div>
|
| 296 |
+
<div className="space-y-2">
|
| 297 |
+
<h2 className="text-2xl font-black tracking-tight">{t.communityTitle}</h2>
|
| 298 |
+
<p className="text-muted-foreground max-w-sm font-medium">{t.groupChatDesc}</p>
|
| 299 |
+
</div>
|
| 300 |
+
<div className="grid grid-cols-2 gap-4 w-full max-w-md">
|
| 301 |
+
<div className="p-6 rounded-3xl bg-primary/5 border border-primary/10 text-center">
|
| 302 |
+
<Bot className="h-8 w-8 mx-auto mb-2 text-primary" />
|
| 303 |
+
<p className="text-xs font-bold">{t.aiLabel}</p>
|
| 304 |
+
</div>
|
| 305 |
+
<div className="p-6 rounded-3xl bg-purple-500/5 border border-purple-500/10 text-center">
|
| 306 |
+
<UserIcon className="h-8 w-8 mx-auto mb-2 text-purple-500" />
|
| 307 |
+
<p className="text-xs font-bold">{t.humanLabel}</p>
|
| 308 |
+
</div>
|
| 309 |
+
</div>
|
| 310 |
+
</div>
|
| 311 |
+
)}
|
| 312 |
+
</div>
|
| 313 |
+
</div>
|
| 314 |
+
);
|
| 315 |
+
};
|
src/components/header.tsx
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
|
| 2 |
'use client';
|
| 3 |
import { ThemeToggle } from '@/components/theme-toggle';
|
| 4 |
-
import {
|
| 5 |
import Link from 'next/link';
|
| 6 |
import { Button } from './ui/button';
|
| 7 |
import { useAuth } from '@/contexts/auth-context';
|
|
@@ -36,10 +36,9 @@ export function Header({ title }: HeaderProps) {
|
|
| 36 |
|
| 37 |
const NavLinks = ({ mobile = false }: { mobile?: boolean }) => {
|
| 38 |
const links = [
|
| 39 |
-
{ href: '/', icon:
|
| 40 |
{ href: '/world', icon: Compass, label: t.worldTitle, color: 'text-purple-500', bg: 'hover:bg-purple-500/10' },
|
| 41 |
{ href: '/reels', icon: Play, label: t.reelsTitle, color: 'text-orange-500', bg: 'hover:bg-orange-500/10' },
|
| 42 |
-
{ href: '/group-chat', icon: Users, label: t.groupChatTitle, color: 'text-blue-500', bg: 'hover:bg-blue-500/10' },
|
| 43 |
{ href: '/voice-chat', icon: Mic, label: t.voiceChatTitle, color: 'text-red-500', bg: 'hover:bg-red-500/10' },
|
| 44 |
];
|
| 45 |
|
|
@@ -98,7 +97,7 @@ export function Header({ title }: HeaderProps) {
|
|
| 98 |
|
| 99 |
<Link href="/" className="flex items-center gap-2 group">
|
| 100 |
<div className="flex h-10 w-10 items-center justify-center rounded-2xl bg-primary text-primary-foreground shadow-lg radiant-glow group-hover:scale-110 transition-all">
|
| 101 |
-
<
|
| 102 |
</div>
|
| 103 |
<h1 className="text-xl font-black tracking-tighter hidden sm:block bg-clip-text text-transparent bg-gradient-to-r from-primary to-purple-500">
|
| 104 |
{t.appTitle}
|
|
|
|
| 1 |
|
| 2 |
'use client';
|
| 3 |
import { ThemeToggle } from '@/components/theme-toggle';
|
| 4 |
+
import { Users, Mic, LogOut, User as UserIcon, Languages, Compass, Play, Menu, Cpu, Sparkles, Zap, MessageCircle } from 'lucide-react';
|
| 5 |
import Link from 'next/link';
|
| 6 |
import { Button } from './ui/button';
|
| 7 |
import { useAuth } from '@/contexts/auth-context';
|
|
|
|
| 36 |
|
| 37 |
const NavLinks = ({ mobile = false }: { mobile?: boolean }) => {
|
| 38 |
const links = [
|
| 39 |
+
{ href: '/', icon: Users, label: t.communityTitle, color: 'text-primary', bg: 'hover:bg-primary/10' },
|
| 40 |
{ href: '/world', icon: Compass, label: t.worldTitle, color: 'text-purple-500', bg: 'hover:bg-purple-500/10' },
|
| 41 |
{ href: '/reels', icon: Play, label: t.reelsTitle, color: 'text-orange-500', bg: 'hover:bg-orange-500/10' },
|
|
|
|
| 42 |
{ href: '/voice-chat', icon: Mic, label: t.voiceChatTitle, color: 'text-red-500', bg: 'hover:bg-red-500/10' },
|
| 43 |
];
|
| 44 |
|
|
|
|
| 97 |
|
| 98 |
<Link href="/" className="flex items-center gap-2 group">
|
| 99 |
<div className="flex h-10 w-10 items-center justify-center rounded-2xl bg-primary text-primary-foreground shadow-lg radiant-glow group-hover:scale-110 transition-all">
|
| 100 |
+
<MessageCircle className="h-6 w-6" />
|
| 101 |
</div>
|
| 102 |
<h1 className="text-xl font-black tracking-tighter hidden sm:block bg-clip-text text-transparent bg-gradient-to-r from-primary to-purple-500">
|
| 103 |
{t.appTitle}
|
src/lib/translations.ts
CHANGED
|
@@ -6,11 +6,11 @@ export const translations = {
|
|
| 6 |
appTitle: 'أفادورا وورلد',
|
| 7 |
worldTitle: 'صدى الأثير',
|
| 8 |
reelsTitle: 'أثير فيو',
|
| 9 |
-
|
| 10 |
voiceChatTitle: 'محادثة صوتية',
|
| 11 |
profileTitle: 'الملف الشخصي',
|
| 12 |
loginTitle: 'تسجيل الدخول',
|
| 13 |
-
chatTab: 'الد
|
| 14 |
logout: 'تسجيل الخروج',
|
| 15 |
welcome: 'أهلاً بك في أفادورا',
|
| 16 |
chooseLogin: 'اختر الطريقة المناسبة لك للبدء',
|
|
@@ -33,7 +33,7 @@ export const translations = {
|
|
| 33 |
deleteConfirmDesc: 'هذا الإجراء سيقوم بحذف حسابك نهائياً. ستفقد الوصول إلى جميع بياناتك.',
|
| 34 |
cancel: 'إلغاء',
|
| 35 |
confirmDelete: 'نعم، احذف حسابي',
|
| 36 |
-
search: 'بحث...',
|
| 37 |
generatePosts: 'ولّد منشورات',
|
| 38 |
thinking: 'بماذا تفكر يا {name}؟',
|
| 39 |
localContext: 'منشورات مخصصة لمنطقتك',
|
|
@@ -48,14 +48,8 @@ export const translations = {
|
|
| 48 |
apiConsumption: 'استهلاك الـ API اليومي',
|
| 49 |
used: 'المستخدم',
|
| 50 |
remaining: 'المتبقي',
|
| 51 |
-
backToChat: 'العودة لل
|
| 52 |
-
watchingOnly: 'هذه محادثة مشاهدة فقط.
|
| 53 |
-
chooseCanned: 'اختر محادثة جاهزة...',
|
| 54 |
-
writeTopic: 'اكتب موضوعاً لتوليده...',
|
| 55 |
-
generate: 'توليد',
|
| 56 |
-
randomChat: 'محادثة عشوائية',
|
| 57 |
-
startGroupChat: 'ابدأ محادثة جماعية',
|
| 58 |
-
groupChatDesc: 'اختر من المحادثات الجاهزة، أو اكتب موضوعًا، أو اضغط "محادثة عشوائية".',
|
| 59 |
voiceWelcome: 'مرحباً بك في المحادثة الصوتية',
|
| 60 |
voiceDesc: 'اضغط على الميكروفون لبدء التحدث مع زينة',
|
| 61 |
listening: 'تستمع...',
|
|
@@ -75,25 +69,30 @@ export const translations = {
|
|
| 75 |
bio: 'نبذة شخصية',
|
| 76 |
gender: 'الجنس',
|
| 77 |
occupation: 'المهنة',
|
| 78 |
-
interests: 'الاهتمامات
|
| 79 |
interestsPlaceholder: 'فلسفة، تقنية، فن، رياضة...',
|
| 80 |
male: 'ذكر',
|
| 81 |
female: 'أنثى',
|
| 82 |
other: 'آخر',
|
| 83 |
bioPlaceholder: 'أخبرنا عن فلسفتك في الحياة...',
|
| 84 |
-
nextVideo: 'الفيديو التالي',
|
| 85 |
-
prevVideo: 'الفيديو السابق',
|
| 86 |
noVideos: 'لا توجد فيديوهات متاحة حالياً.',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 87 |
},
|
| 88 |
en: {
|
| 89 |
appTitle: 'AvadoraWorld',
|
| 90 |
worldTitle: 'Aether Echo',
|
| 91 |
reelsTitle: 'Aether View',
|
| 92 |
-
|
| 93 |
voiceChatTitle: 'Voice Chat',
|
| 94 |
profileTitle: 'Profile',
|
| 95 |
loginTitle: 'Login',
|
| 96 |
-
chatTab: '
|
| 97 |
logout: 'Logout',
|
| 98 |
welcome: 'Welcome to Avadora',
|
| 99 |
chooseLogin: 'Choose your preferred method to start',
|
|
@@ -116,7 +115,7 @@ export const translations = {
|
|
| 116 |
deleteConfirmDesc: 'This action will permanently delete your account. You will lose access to all your data.',
|
| 117 |
cancel: 'Cancel',
|
| 118 |
confirmDelete: 'Yes, delete my account',
|
| 119 |
-
search: 'Search...',
|
| 120 |
generatePosts: 'Generate Posts',
|
| 121 |
thinking: 'What\'s on your mind, {name}?',
|
| 122 |
localContext: 'Posts customized for your area',
|
|
@@ -131,14 +130,8 @@ export const translations = {
|
|
| 131 |
apiConsumption: 'Daily API Consumption',
|
| 132 |
used: 'Used',
|
| 133 |
remaining: 'Remaining',
|
| 134 |
-
backToChat: 'Back to
|
| 135 |
-
watchingOnly: 'This is a view-only chat.
|
| 136 |
-
chooseCanned: 'Choose a ready conversation...',
|
| 137 |
-
writeTopic: 'Write a topic to generate...',
|
| 138 |
-
generate: 'Generate',
|
| 139 |
-
randomChat: 'Random Chat',
|
| 140 |
-
startGroupChat: 'Start a Group Chat',
|
| 141 |
-
groupChatDesc: 'Choose from ready chats, write a topic, or press "Random Chat".',
|
| 142 |
voiceWelcome: 'Welcome to Voice Chat',
|
| 143 |
voiceDesc: 'Press the microphone to start talking with Zeina',
|
| 144 |
listening: 'Listening...',
|
|
@@ -158,14 +151,19 @@ export const translations = {
|
|
| 158 |
bio: 'Bio',
|
| 159 |
gender: 'Gender',
|
| 160 |
occupation: 'Occupation',
|
| 161 |
-
interests: 'Interests
|
| 162 |
interestsPlaceholder: 'Philosophy, Tech, Art, Sports...',
|
| 163 |
male: 'Male',
|
| 164 |
female: 'Female',
|
| 165 |
other: 'Other',
|
| 166 |
bioPlaceholder: 'Tell us about your philosophy...',
|
| 167 |
-
nextVideo: 'Next Video',
|
| 168 |
-
prevVideo: 'Previous Video',
|
| 169 |
noVideos: 'No videos available right now.',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
}
|
| 171 |
};
|
|
|
|
| 6 |
appTitle: 'أفادورا وورلد',
|
| 7 |
worldTitle: 'صدى الأثير',
|
| 8 |
reelsTitle: 'أثير فيو',
|
| 9 |
+
communityTitle: 'المجتمع',
|
| 10 |
voiceChatTitle: 'محادثة صوتية',
|
| 11 |
profileTitle: 'الملف الشخصي',
|
| 12 |
loginTitle: 'تسجيل الدخول',
|
| 13 |
+
chatTab: 'المحادثات',
|
| 14 |
logout: 'تسجيل الخروج',
|
| 15 |
welcome: 'أهلاً بك في أفادورا',
|
| 16 |
chooseLogin: 'اختر الطريقة المناسبة لك للبدء',
|
|
|
|
| 33 |
deleteConfirmDesc: 'هذا الإجراء سيقوم بحذف حسابك نهائياً. ستفقد الوصول إلى جميع بياناتك.',
|
| 34 |
cancel: 'إلغاء',
|
| 35 |
confirmDelete: 'نعم، احذف حسابي',
|
| 36 |
+
search: 'بحث عن أشخاص...',
|
| 37 |
generatePosts: 'ولّد منشورات',
|
| 38 |
thinking: 'بماذا تفكر يا {name}؟',
|
| 39 |
localContext: 'منشورات مخصصة لمنطقتك',
|
|
|
|
| 48 |
apiConsumption: 'استهلاك الـ API اليومي',
|
| 49 |
used: 'المستخدم',
|
| 50 |
remaining: 'المتبقي',
|
| 51 |
+
backToChat: 'العودة للمجتمع',
|
| 52 |
+
watchingOnly: 'هذه محادثة مشاهدة فقط.',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
voiceWelcome: 'مرحباً بك في المحادثة الصوتية',
|
| 54 |
voiceDesc: 'اضغط على الميكروفون لبدء التحدث مع زينة',
|
| 55 |
listening: 'تستمع...',
|
|
|
|
| 69 |
bio: 'نبذة شخصية',
|
| 70 |
gender: 'الجنس',
|
| 71 |
occupation: 'المهنة',
|
| 72 |
+
interests: 'الاهتمامات',
|
| 73 |
interestsPlaceholder: 'فلسفة، تقنية، فن، رياضة...',
|
| 74 |
male: 'ذكر',
|
| 75 |
female: 'أنثى',
|
| 76 |
other: 'آخر',
|
| 77 |
bioPlaceholder: 'أخبرنا عن فلسفتك في الحياة...',
|
|
|
|
|
|
|
| 78 |
noVideos: 'لا توجد فيديوهات متاحة حالياً.',
|
| 79 |
+
aiLabel: 'ذكاء اصطناعي',
|
| 80 |
+
humanLabel: 'مستخدم حقيقي',
|
| 81 |
+
startChat: 'ابدأ المحادثة الآن',
|
| 82 |
+
online: 'متصل الآن',
|
| 83 |
+
typeMessage: 'اكتب رسالتك هنا...',
|
| 84 |
+
noUsers: 'لا يوجد أشخاص متاحون حالياً.',
|
| 85 |
+
chatWith: 'تحدث مع {name}'
|
| 86 |
},
|
| 87 |
en: {
|
| 88 |
appTitle: 'AvadoraWorld',
|
| 89 |
worldTitle: 'Aether Echo',
|
| 90 |
reelsTitle: 'Aether View',
|
| 91 |
+
communityTitle: 'Community',
|
| 92 |
voiceChatTitle: 'Voice Chat',
|
| 93 |
profileTitle: 'Profile',
|
| 94 |
loginTitle: 'Login',
|
| 95 |
+
chatTab: 'Chats',
|
| 96 |
logout: 'Logout',
|
| 97 |
welcome: 'Welcome to Avadora',
|
| 98 |
chooseLogin: 'Choose your preferred method to start',
|
|
|
|
| 115 |
deleteConfirmDesc: 'This action will permanently delete your account. You will lose access to all your data.',
|
| 116 |
cancel: 'Cancel',
|
| 117 |
confirmDelete: 'Yes, delete my account',
|
| 118 |
+
search: 'Search for people...',
|
| 119 |
generatePosts: 'Generate Posts',
|
| 120 |
thinking: 'What\'s on your mind, {name}?',
|
| 121 |
localContext: 'Posts customized for your area',
|
|
|
|
| 130 |
apiConsumption: 'Daily API Consumption',
|
| 131 |
used: 'Used',
|
| 132 |
remaining: 'Remaining',
|
| 133 |
+
backToChat: 'Back to Community',
|
| 134 |
+
watchingOnly: 'This is a view-only chat.',
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 135 |
voiceWelcome: 'Welcome to Voice Chat',
|
| 136 |
voiceDesc: 'Press the microphone to start talking with Zeina',
|
| 137 |
listening: 'Listening...',
|
|
|
|
| 151 |
bio: 'Bio',
|
| 152 |
gender: 'Gender',
|
| 153 |
occupation: 'Occupation',
|
| 154 |
+
interests: 'Interests',
|
| 155 |
interestsPlaceholder: 'Philosophy, Tech, Art, Sports...',
|
| 156 |
male: 'Male',
|
| 157 |
female: 'Female',
|
| 158 |
other: 'Other',
|
| 159 |
bioPlaceholder: 'Tell us about your philosophy...',
|
|
|
|
|
|
|
| 160 |
noVideos: 'No videos available right now.',
|
| 161 |
+
aiLabel: 'AI Assistant',
|
| 162 |
+
humanLabel: 'Real User',
|
| 163 |
+
startChat: 'Start Chatting Now',
|
| 164 |
+
online: 'Online Now',
|
| 165 |
+
typeMessage: 'Type your message...',
|
| 166 |
+
noUsers: 'No people found.',
|
| 167 |
+
chatWith: 'Chat with {name}'
|
| 168 |
}
|
| 169 |
};
|