import React, { useState, useEffect, useRef, useMemo } from 'react';
import { initializeApp } from 'firebase/app';
import {
getAuth,
signInAnonymously,
onAuthStateChanged,
signInWithCustomToken
} from 'firebase/auth';
import {
getFirestore,
collection,
addDoc,
onSnapshot,
doc,
updateDoc,
deleteDoc,
arrayUnion,
arrayRemove,
increment,
setDoc,
getDoc
} from 'firebase/firestore';
import {
Heart,
MessageCircle,
Repeat,
Share,
ArrowLeft,
Trash2,
X,
Home as HomeIcon,
Search,
Bell,
Mail,
User,
UserPlus,
UserCheck,
Send,
Calendar,
MapPin,
Link as LinkIcon,
Camera
} from 'lucide-react';
// --- Firebase Initialization ---
const firebaseConfig = JSON.parse(__firebase_config);
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
// --- Utilities ---
const formatTime = (timestamp) => {
if (!timestamp) return '';
const date = new Date(timestamp);
const now = new Date();
const diffInSeconds = Math.floor((now - date) / 1000);
if (diffInSeconds < 60) return ${diffInSeconds}s;
if (diffInSeconds < 3600) return ${Math.floor(diffInSeconds / 60)}m;
if (diffInSeconds < 86400) return ${Math.floor(diffInSeconds / 3600)}h;
return date.toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
};
const getRandomColor = (uid) => {
const colors = [
'bg-sky-600', 'bg-emerald-600', 'bg-violet-600',
'bg-rose-600', 'bg-amber-600', 'bg-teal-600', 'bg-indigo-600'
];
let hash = 0;
for (let i = 0; i < uid.length; i++) hash = uid.charCodeAt(i) + ((hash << 5) - hash);
return colors[Math.abs(hash) % colors.length];
};
const getCoverColor = (uid) => {
const colors = [
'bg-slate-700', 'bg-zinc-700', 'bg-neutral-700',
'bg-stone-700', 'bg-blue-900', 'bg-indigo-900'
];
let hash = 0;
for (let i = 0; i < uid.length; i++) hash = uid.charCodeAt(i) + ((hash << 5) - hash);
return colors[Math.abs(hash) % colors.length];
};
// --- Components ---
const Avatar = ({ uid, username, size = "md", onClick, className = "" }) => {
const s = size === "xl" ? "w-24 h-24 text-4xl border-4 border-black" : size === "lg" ? "w-12 h-12 text-lg" : size === "sm" ? "w-8 h-8 text-xs" : "w-10 h-10 text-base";
return (
<div
onClick={onClick}
className=${s} rounded-full ${getRandomColor(uid)} flex-shrink-0 flex items-center justify-center font-bold text-white shadow-sm select-none hover:opacity-90 transition-opacity cursor-pointer ${className}}
>
{username ? username[0].toUpperCase() : '?'}
</div>
);
};
const ActionButton = ({ icon: Icon, count, active, type, onClick }) => {
const colors = {
reply: { base: 'text-[#71767B]', hover: 'group-hover:text-[#1D9BF0]', bg: 'group-hover:bg-[#1D9BF0]/10', active: 'text-[#1D9BF0]' },
repost: { base: 'text-[#71767B]', hover: 'group-hover:text-[#00BA7C]', bg: 'group-hover:bg-[#00BA7C]/10', active: 'text-[#00BA7C]' },
like: { base: 'text-[#71767B]', hover: 'group-hover:text-[#F91880]', bg: 'group-hover:bg-[#F91880]/10', active: 'text-[#F91880]' },
share: { base: 'text-[#71767B]', hover: 'group-hover:text-[#1D9BF0]', bg: 'group-hover:bg-[#1D9BF0]/10', active: 'text-[#1D9BF0]' },
delete: { base: 'text-[#71767B]', hover: 'group-hover:text-red-500', bg: 'group-hover:bg-red-500/10', active: 'text-red-500' },
mail: { base: 'text-[#71767B]', hover: 'group-hover:text-[#1D9BF0]', bg: 'group-hover:bg-[#1D9BF0]/10', active: 'text-[#1D9BF0]' }
};
const style = colors[type] || colors.share;
const isHeart = type === 'like';
const isRepost = type === 'repost';
return (
<button
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onClick && onClick(e);
}}
className=group flex items-center gap-1.5 transition-colors duration-200 outline-none ${active ? style.active : style.base} ${style.hover} z-10 relative}
>
<div className=relative p-2 rounded-full transition-colors duration-200 ${style.bg}}>
<Icon
size={18}
className={`
transition-transform duration-200
${active && isHeart ? 'animate-heart-pop' : ''}
${active && isRepost ? 'text-[#00BA7C]' : ''}
`}
fill={active && isHeart ? "currentColor" : "none"}
strokeWidth={active && isHeart ? 0 : 2}
/>
{active && isHeart && (
<div className="absolute inset-0 rounded-full animate-heart-ring border-2 border-[#F91880] opacity-0" />
)}
</div>
{count !== undefined && (
<span className=text-[13px] tabular-nums transition-colors duration-200 ${active ? style.active : style.hover}}>
{count > 0 ? count : ''}
</span>
)}
</button>
);
};
// Bottom Nav Item
const NavItem = ({ icon: Icon, active, onClick, badge }) => (
<button
onClick={onClick}
className="flex-1 flex justify-center items-center py-3 hover:bg-white/5 transition-colors active:scale-95 duration-100 relative"
>
<div className="relative">
<Icon
size={26}
strokeWidth={active ? 3 : 2}
className={active ? 'text-[#E7E9EA]' : 'text-[#71767B]'}
/>
{active && <div className="absolute -bottom-1 left-1/2 -translate-x-1/2 w-1 h-1 bg-[#E7E9EA] rounded-full hidden"></div>}
{badge > 0 && (
<div className="absolute -top-1 -right-1 bg-[#1D9BF0] text-white text-[10px] font-bold px-1 min-w-[16px] h-[16px] rounded-full flex items-center justify-center border border-black">
{badge}
</div>
)}
</div>
</button>
);
// --- Main App ---
export default function App() {
const [user, setUser] = useState(null);
const [username, setUsername] = useState('');
// Extra Profile Fields
const [bio, setBio] = useState('');
const [location, setLocation] = useState('');
const [website, setWebsite] = useState('');
// Navigation State
const [view, setView] = useState('feed');
const [activeTab, setActiveTab] = useState('home');
const [feedTab, setFeedTab] = useState('foryou');
// Data State
const [activeTweet, setActiveTweet] = useState(null);
const [activeChatUser, setActiveChatUser] = useState(null);
const [tweets, setTweets] = useState([]);
const [comments, setComments] = useState([]);
const [notifications, setNotifications] = useState([]);
const [messages, setMessages] = useState([]);
const [following, setFollowing] = useState(new Set());
const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
// UI States
const [tweetText, setTweetText] = useState('');
const [messageText, setMessageText] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [toast, setToast] = useState(null);
// Editing Profile States
const [isEditingProfile, setIsEditingProfile] = useState(false);
const [editName, setEditName] = useState('');
const [editBio, setEditBio] = useState('');
const [editLocation, setEditLocation] = useState('');
const [editWebsite, setEditWebsite] = useState('');
const scrollContainerRef = useRef(null);
// --- Auth & Init ---
useEffect(() => {
const initAuth = async () => {
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
await signInWithCustomToken(auth, __initial_auth_token);
} else {
await signInAnonymously(auth);
}
};
initAuth();
return onAuthStateChanged(auth, (u) => {
setUser(u);
if (u) {
// Load Profile Data
const storedName = localStorage.getItemuser_name_${u.uid});
const storedBio = localStorage.getItemuser_bio_${u.uid});
const storedLoc = localStorage.getItemuser_location_${u.uid});
const storedWeb = localStorage.getItemuser_website_${u.uid});
setUsername(storedName || User${u.uid.substring(0, 4)});
setBio(storedBio || '');
setLocation(storedLoc || '');
setWebsite(storedWeb || '');
}
});
}, []);
const showToast = (msg) => {
setToast(msg);
setTimeout(() => setToast(null), 3000);
};
// --- Data Fetching ---
// 1. Fetch Tweets (Global)
useEffect(() => {
if (!user) return;
const q = collection(db, 'artifacts', appId, 'public', 'data', 'tweets');
const unsub = onSnapshot(q, (snap) => {
const data = snap.docs.map(d => ({ id: d.id, ...d.data() }));
data.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
setTweets(data);
setLoading(false);
if (activeTweet) {
const updated = data.find(t => t.id === activeTweet.id);
if (updated) setActiveTweet(updated);
}
});
return () => unsub();
}, [user, activeTweet?.id]);
// 2. Fetch Comments
useEffect(() => {
if (!activeTweet || view !== 'thread') {
setComments([]);
return;
}
const q = collection(db, 'artifacts', appId, 'public', 'data', 'comments');
const unsub = onSnapshot(q, (snap) => {
const allComments = snap.docs.map(d => ({ id: d.id, ...d.data() }));
const threadComments = allComments.filter(c => c.tweetId === activeTweet.id);
threadComments.sort((a, b) => (b.createdAt || 0) - (a.createdAt || 0));
setComments(threadComments);
});
return () => unsub();
}, [activeTweet, view]);
// 3. Fetch Notifications
useEffect(() => {
if (!user) return;
const q = collection(db, 'artifacts', appId, 'users', user.uid, 'notifications');
const unsub = onSnapshot(q, (snap) => {
const data = snap.docs.map(d => ({ id: d.id, ...d.data() }));
data.sort((a, b) => b.createdAt - a.createdAt);
setNotifications(data);
});
return () => unsub();
}, [user]);
// 4. Fetch Following
useEffect(() => {
if (!user) return;
const q = collection(db, 'artifacts', appId, 'users', user.uid, 'following');
const unsub = onSnapshot(q, (snap) => {
const ids = new Set(snap.docs.map(d => d.id));
setFollowing(ids);
});
return () => unsub();
}, [user]);
// 5. Fetch Messages
useEffect(() => {
if (!user) return;
const q = collection(db, 'artifacts', appId, 'public', 'data', 'direct_messages');
const unsub = onSnapshot(q, (snap) => {
const allMsgs = snap.docs.map(d => ({ id: d.id, ...d.data() }));
const myMsgs = allMsgs.filter(m => m.from === user.uid || m.to === user.uid);
myMsgs.sort((a, b) => a.createdAt - b.createdAt);
setMessages(myMsgs);
});
return () => unsub();
}, [user]);
// --- Computed Data ---
const filteredTweets = useMemo(() => {
let result = tweets;
if (activeTab === 'profile') {
result = result.filter(t => t.uid === user?.uid);
} else if (activeTab === 'search' && searchQuery) {
const lowerQ = searchQuery.toLowerCase();
result = result.filter(t => t.text?.toLowerCase().includes(lowerQ) || t.username?.toLowerCase().includes(lowerQ));
} else if (activeTab === 'home' && feedTab === 'following') {
result = result.filter(t => following.has(t.uid));
}
return result;
}, [tweets, activeTab, feedTab, following, searchQuery, user]);
const conversations = useMemo(() => {
const map = new Map();
messages.forEach(msg => {
const otherUid = msg.from === user.uid ? msg.to : msg.from;
const otherName = msg.from === user.uid ? msg.toUsername : msg.fromUsername;
if (!map.has(otherUid)) {
map.set(otherUid, {
uid: otherUid,
username: otherName || 'User',
lastMsg: msg,
unread: 0
});
} else {
const conv = map.get(otherUid);
if (msg.createdAt > conv.lastMsg.createdAt) {
conv.lastMsg = msg;
conv.username = otherName || conv.username;
}
}
});
return Array.from(map.values()).sort((a, b) => b.lastMsg.createdAt - a.lastMsg.createdAt);
}, [messages, user]);
const currentChatMessages = useMemo(() => {
if (!activeChatUser) return [];
return messages.filter(m =>
(m.from === user.uid && m.to === activeChatUser.uid) ||
(m.from === activeChatUser.uid && m.to === user.uid)
);
}, [messages, activeChatUser, user]);
// --- Actions ---
const handlePost = async (isReply = false) => {
if (!tweetText.trim()) return;
setIsSubmitting(true);
try {
if (isReply && activeTweet) {
await addDoc(collection(db, 'artifacts', appId, 'public', 'data', 'comments'), {
text: tweetText,
uid: user.uid,
username,
createdAt: Date.now(),
tweetId: activeTweet.id,
likes: []
});
await updateDoc(doc(db, 'artifacts', appId, 'public', 'data', 'tweets', activeTweet.id), {
replyCount: increment(1)
});
showToast("Reply sent");
} else {
await addDoc(collection(db, 'artifacts', appId, 'public', 'data', 'tweets'), {
text: tweetText,
uid: user.uid,
username,
createdAt: Date.now(),
likes: [],
reposts: [],
replyCount: 0
});
showToast("Posted");
}
setTweetText('');
} catch (e) {
console.error(e);
showToast("Error posting");
}
setIsSubmitting(false);
};
const handleLike = async (docId, likes, authorUid, isComment = false) => {
const isLiked = likes.includes(user.uid);
const col = isComment ? 'comments' : 'tweets';
const ref = doc(db, 'artifacts', appId, 'public', 'data', col, docId);
await updateDoc(ref, {
likes: isLiked ? arrayRemove(user.uid) : arrayUnion(user.uid)
});
};
const handleRepost = async (tweet) => {
const isReposted = tweet.reposts?.includes(user.uid);
const ref = doc(db, 'artifacts', appId, 'public', 'data', 'tweets', tweet.id);
await updateDoc(ref, {
reposts: isReposted ? arrayRemove(user.uid) : arrayUnion(user.uid)
});
showToast(isReposted ? "Repost removed" : "Reposted");
};
const handleFollow = async (targetUid) => {
if (targetUid === user.uid) return;
const isFollowing = following.has(targetUid);
const ref = doc(db, 'artifacts', appId, 'users', user.uid, 'following', targetUid);
if (isFollowing) {
await deleteDoc(ref);
showToast("Unfollowed");
} else {
await setDoc(ref, { since: Date.now() });
showToast("Following");
}
};
const handleSendMessage = async () => {
if (!messageText.trim() || !activeChatUser) return;
try {
await addDoc(collection(db, 'artifacts', appId, 'public', 'data', 'direct_messages'), {
text: messageText,
from: user.uid,
fromUsername: username,
to: activeChatUser.uid,
toUsername: activeChatUser.username,
createdAt: Date.now()
});
setMessageText('');
} catch (e) {
showToast("Failed to send");
}
};
const handleDelete = async (id, isComment = false) => {
if (!confirm("Delete this?")) return;
try {
const col = isComment ? 'comments' : 'tweets';
await deleteDoc(doc(db, 'artifacts', appId, 'public', 'data', col, id));
if (isComment && activeTweet) {
await updateDoc(doc(db, 'artifacts', appId, 'public', 'data', 'tweets', activeTweet.id), {
replyCount: increment(-1)
});
}
if (!isComment && view === 'thread') setView('feed');
showToast("Deleted");
} catch (e) { showToast("Error deleting"); }
};
const openEditProfile = () => {
setEditName(username);
setEditBio(bio);
setEditLocation(location);
setEditWebsite(website);
setIsEditingProfile(true);
};
const handleUpdateProfile = (e) => {
e.preventDefault();
if (!editName.trim()) return;
// Save to Local Storage for consistency/persistence on same device
localStorage.setItemuser_name_${user.uid}, editName);
localStorage.setItemuser_bio_${user.uid}, editBio);
localStorage.setItemuser_location_${user.uid}, editLocation);
localStorage.setItemuser_website_${user.uid}, editWebsite);
// Update State
setUsername(editName);
setBio(editBio);
setLocation(editLocation);
setWebsite(editWebsite);
setIsEditingProfile(false);
showToast("Profile updated");
};
// --- Navigation Actions ---
const openThread = (tweet) => {
setActiveTweet(tweet);
setView('thread');
setTweetText('');
};
const openChat = (uid, uName) => {
setActiveChatUser({ uid, username: uName });
setView('chat');
setActiveTab('messages');
};
// --- Render Helpers ---
const TweetCard = ({ data, isComment = false, isMain = false }) => {
const isLiked = data.likes?.includes(user?.uid) ?? false;
const isReposted = !isComment && (data.reposts?.includes(user?.uid) ?? false);
const canDelete = user?.uid === data.uid;
const isFollowing = following.has(data.uid);
return (
<article
onClick={() => !isMain && !isComment && openThread(data)}
className={`
${isMain ? 'p-4' : 'px-4 py-3 border-b border-[#2F3336] hover:bg-[#080808] cursor-pointer'}
transition-colors duration-200
`}
>
<div className=flex ${isMain ? 'flex-col gap-3' : 'gap-3'}}>
{!isMain && <Avatar uid={data.uid} username={data.username} onClick={(e) => { e.stopPropagation(); openChat(data.uid, data.username); }} />}
<div className="flex-1 min-w-0">
{/* Header */}
<div className="flex justify-between items-start relative z-0">
<div className="flex items-center gap-2 mb-1 min-w-0">
{isMain && <Avatar uid={data.uid} username={data.username} size="lg" onClick={() => openChat(data.uid, data.username)} />}
<div className=flex ${isMain ? 'flex-col' : 'gap-1.5 items-center'} min-w-0}>
<span className="font-bold text-white truncate text-[15px]">{data.username}</span>
<span className="text-[#71767B] text-[15px] truncate">@{data.username?.toLowerCase().replace(/\s/g, '')}</span>
{!isMain && <span className="text-[#71767B] text-[15px]">· {formatTime(data.createdAt)}</span>}
</div>
{/* Follow Button */}
{user.uid !== data.uid && (
<button
onClick={(e) => { e.stopPropagation(); handleFollow(data.uid); }}
className=ml-2 p-1 rounded-full transition-colors ${isFollowing ? 'text-[#1D9BF0] bg-[#1D9BF0]/10' : 'text-[#71767B] hover:bg-[#1D9BF0]/10 hover:text-[#1D9BF0]'}}
>
{isFollowing ? <UserCheck size={16} /> : <UserPlus size={16} />}
</button>
)}
</div>
{canDelete && !isMain && (
<div className="-mr-2 -mt-1">
<ActionButton icon={Trash2} type="delete" onClick={() => handleDelete(data.id, isComment)} />
</div>
)}
</div>
{/* Content */}
<div className=text-white whitespace-pre-wrap leading-normal break-words ${isMain ? 'text-[17px] mt-3 mb-3' : 'text-[15px] mt-0.5'}}>
{data.text}
</div>
{/* Actions Bar */}
<div className=flex justify-between ${isMain ? 'justify-around py-2 border-b border-[#2F3336]' : 'max-w-[425px] mt-3 -ml-2'}}>
<ActionButton icon={MessageCircle} type="reply" count={data.replyCount || 0} onClick={() => !isMain && !isComment && openThread(data)} />
{!isComment && <ActionButton icon={Repeat} type="repost" count={data.reposts?.length || 0} active={isReposted} onClick={() => handleRepost(data)} />}
<ActionButton icon={Heart} type="like" count={data.likes?.length || 0} active={isLiked} onClick={() => handleLike(data.id, data.likes || [], data.uid, isComment)} />
<ActionButton icon={Mail} type="mail" onClick={() => openChat(data.uid, data.username)} />
{isMain && canDelete && <ActionButton icon={Trash2} type="delete" onClick={() => handleDelete(data.id, isComment)} />}
</div>
</div>
</div>
</article>
);
};
const NotificationItem = ({ note }) => {
let icon = <Heart size={16} fill="currentColor" className="text-[#F91880]" />;
let text = "liked your post";
if (note.type === 'repost') { icon = <Repeat size={16} className="text-[#00BA7C]" />; text = "reposted your post"; }
if (note.type === 'reply') { icon = <MessageCircle size={16} className="text-[#1D9BF0]" />; text = "replied to you"; }
if (note.type === 'follow') { icon = <UserPlus size={16} className="text-[#1D9BF0]" />; text = "followed you"; }
return (
<div className="p-4 border-b border-[#2F3336] hover:bg-[#080808] flex gap-3 animate-fade-in">
<div className="pt-1 w-8 flex justify-end">{icon}</div>
<div className="flex-1">
<Avatar uid={note.fromUid} username={note.fromUsername} size="sm" onClick={() => openChat(note.fromUid, note.fromUsername)} />
<p className="mt-2 text-[15px]">
<span className="font-bold">{note.fromUsername}</span> <span className="text-[#71767B]">{text}</span>
</p>
{note.text && <p className="text-[#71767B] mt-1 text-[14px] line-clamp-2">"{note.text}"</p>}
</div>
</div>
);
};
// --- Render Main ---
if (loading) return (
<div className="h-screen w-full bg-black flex items-center justify-center">
<div className="w-8 h-8 border-[3px] border-[#1D9BF0] border-t-transparent rounded-full animate-spin"></div>
</div>
);
return (
<div className="flex justify-center min-h-screen bg-black text-white font-sans antialiased selection:bg-[#1D9BF0] selection:text-white">
{/* Edit Profile Modal */}
{isEditingProfile && (
<div className="fixed inset-0 z-[60] flex items-center justify-center bg-[#5B7083]/40 p-4">
<div className="bg-black w-full max-w-lg rounded-2xl shadow-2xl border border-[#2F3336] animate-scale-in flex flex-col max-h-[90vh] overflow-hidden">
{/* Modal Header */}
<div className="flex justify-between items-center px-4 py-3 border-b border-[#2F3336]">
<div className="flex items-center gap-4">
<button onClick={() => setIsEditingProfile(false)} className="hover:bg-[#EFF3F4]/10 p-2 rounded-full -ml-2"><X size={20}/></button>
<h2 className="font-bold text-xl">Edit Profile</h2>
</div>
<button onClick={handleUpdateProfile} className="bg-[#E7E9EA] text-black px-5 py-1.5 rounded-full font-bold text-sm hover:bg-white transition-colors">Save</button>
</div>
{/* Modal Body */}
<div className="overflow-y-auto p-0 flex-1 custom-scrollbar">
{/* Visual Header */}
<div className="relative mb-6">
<div className="h-32 bg-slate-700 opacity-80 flex items-center justify-center">
<div className="bg-black/40 p-3 rounded-full hover:bg-black/50 cursor-pointer backdrop-blur-sm"><Camera size={22} className="text-white"/></div>
</div>
<div className="absolute -bottom-8 left-4 p-1 bg-black rounded-full">
<div className="relative">
<Avatar uid={user.uid} size="xl" className="border-0" />
<div className="absolute inset-0 flex items-center justify-center bg-black/30 rounded-full hover:bg-black/40 cursor-pointer">
<Camera size={20} className="text-white"/>
</div>
</div>
</div>
</div>
{/* Form Fields */}
<div className="mt-12 px-4 space-y-5 pb-6">
{/* Name */}
<div className="border border-[#333639] rounded px-3 py-2 focus-within:border-[#1D9BF0] focus-within:ring-1 focus-within:ring-[#1D9BF0] transition-all bg-black group">
<div className="flex justify-between">
<label className="text-xs text-[#71767B] group-focus-within:text-[#1D9BF0]">Name</label>
<span className="text-xs text-[#71767B]">{editName.length} / 50</span>
</div>
<input
value={editName}
onChange={e => setEditName(e.target.value)}
className="bg-transparent w-full text-white outline-none text-[17px] mt-1"
maxLength={50}
/>
</div>
{/* Bio */}
<div className="border border-[#333639] rounded px-3 py-2 focus-within:border-[#1D9BF0] focus-within:ring-1 focus-within:ring-[#1D9BF0] transition-all bg-black group">
<div className="flex justify-between">
<label className="text-xs text-[#71767B] group-focus-within:text-[#1D9BF0]">Bio</label>
<span className="text-xs text-[#71767B]">{editBio.length} / 160</span>
</div>
<textarea
value={editBio}
onChange={e => setEditBio(e.target.value)}
className="bg-transparent w-full text-white outline-none text-[17px] mt-1 resize-none h-24"
maxLength={160}
/>
</div>
{/* Location */}
<div className="border border-[#333639] rounded px-3 py-2 focus-within:border-[#1D9BF0] focus-within:ring-1 focus-within:ring-[#1D9BF0] transition-all bg-black group">
<label className="text-xs text-[#71767B] block group-focus-within:text-[#1D9BF0]">Location</label>
<input
value={editLocation}
onChange={e => setEditLocation(e.target.value)}
className="bg-transparent w-full text-white outline-none text-[17px] mt-1"
maxLength={30}
/>
</div>
{/* Website */}
<div className="border border-[#333639] rounded px-3 py-2 focus-within:border-[#1D9BF0] focus-within:ring-1 focus-within:ring-[#1D9BF0] transition-all bg-black group">
<label className="text-xs text-[#71767B] block group-focus-within:text-[#1D9BF0]">Website</label>
<input
value={editWebsite}
onChange={e => setEditWebsite(e.target.value)}
className="bg-transparent w-full text-white outline-none text-[17px] mt-1"
maxLength={100}
placeholder="Add your website"
/>
</div>
</div>
</div>
</div>
</div>
)}
{toast && (
<div className="fixed bottom-20 left-1/2 -translate-x-1/2 bg-[#1D9BF0] text-white px-5 py-2.5 rounded-full shadow-[0_8px_30px_rgb(0,0,0,0.3)] z-50 text-[15px] font-medium animate-toast-up">
{toast}
</div>
)}
<div className="w-full max-w-[600px] border-x border-[#2F3336] flex flex-col h-screen relative">
{/* === HEADER === */}
{view === 'thread' || view === 'chat' ? (
<header className="sticky top-0 z-20 bg-black/75 backdrop-blur-md border-b border-[#2F3336] px-4 h-[53px] flex items-center gap-6">
<button
onClick={() => {
if (view === 'chat') {
setActiveChatUser(null);
setView('feed');
} else {
setView('feed');
}
}}
className="p-2 -ml-2 hover:bg-[#EFF3F4]/10 rounded-full transition-colors group"
>
<ArrowLeft size={20} className="text-white group-hover:text-white" />
</button>
<h2 className="text-[20px] font-bold leading-6 text-[#E7E9EA]">
{view === 'chat' ? (activeChatUser?.username || 'Chat') : 'Post'}
</h2>
</header>
) : (
<header className="sticky top-0 z-20 bg-black/75 backdrop-blur-md border-b border-[#2F3336] transition-all">
<div className="px-4 h-[53px] flex items-center justify-between relative">
{/* Left: Avatar */}
<div className="z-30">
<button onClick={() => { setActiveTab('profile'); setView('feed'); }} className="p-1 rounded-full hover:bg-[#EFF3F4]/10 transition-colors">
<Avatar uid={user?.uid || 'anon'} username={username} size="sm" />
</button>
</div>
{/* Center: Title/Search */}
<div className="absolute left-1/2 -translate-x-1/2 flex items-center justify-center">
{activeTab === 'home' && <div className="w-8 h-8"></div>}
{activeTab === 'search' && (
<input
className="bg-[#202327] rounded-full py-2 px-5 text-[15px] w-64 text-white placeholder-[#71767B] focus:outline-none focus:bg-black focus:border focus:border-[#1D9BF0] transition-all"
placeholder="Search"
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
/>
)}
{activeTab === 'notifications' && <span className="font-bold text-[17px]">Notifications</span>}
{activeTab === 'messages' && <span className="font-bold text-[17px]">Messages</span>}
{activeTab === 'profile' && (
<div className="flex flex-col items-center">
<span className="font-bold text-[17px]">{username}</span>
<span className="text-xs text-[#71767B]">{filteredTweets.length} posts</span>
</div>
)}
</div>
<div className="w-8"></div>
</div>
{/* Home Tabs */}
{activeTab === 'home' && (
<div className="flex w-full mt-1">
<button onClick={() => setFeedTab('foryou')} className="flex-1 h-[53px] hover:bg-[#EFF3F4]/10 transition-colors relative flex justify-center items-center cursor-pointer">
<div className="relative h-full flex items-center">
<span className=text-[15px] font-bold ${feedTab === 'foryou' ? 'text-[#E7E9EA]' : 'text-[#71767B]'}}>For you</span>
{feedTab === 'foryou' && <div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-14 h-[4px] bg-[#1D9BF0] rounded-full"></div>}
</div>
</button>
<button onClick={() => setFeedTab('following')} className="flex-1 h-[53px] hover:bg-[#EFF3F4]/10 transition-colors relative flex justify-center items-center cursor-pointer">
<div className="relative h-full flex items-center">
<span className=text-[15px] font-bold ${feedTab === 'following' ? 'text-[#E7E9EA]' : 'text-[#71767B]'}}>Following</span>
{feedTab === 'following' && <div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-[70px] h-[4px] bg-[#1D9BF0] rounded-full"></div>}
</div>
</button>
</div>
)}
</header>
)}
{/* === SCROLLABLE CONTENT === */}
<div ref={scrollContainerRef} className="flex-1 overflow-y-auto custom-scrollbar scroll-smooth">
{/* PROFILE VIEW */}
{activeTab === 'profile' && view === 'feed' && (
<div className="animate-fade-in pb-20">
{/* Banner */}
<div className=h-32 w-full ${getCoverColor(user.uid)} relative}></div>
{/* Profile Header */}
<div className="px-4 relative mb-4">
<div className="flex justify-between items-start">
<div className="-mt-10 mb-3">
<Avatar uid={user.uid} username={username} size="xl" />
</div>
<button
onClick={openEditProfile}
className="mt-3 border border-[#536471] text-white font-bold px-4 py-1.5 rounded-full text-[15px] hover:bg-white/10 transition-colors"
>
Edit profile
</button>
</div>
<div>
<h2 className="text-[20px] font-extrabold leading-6 text-[#E7E9EA]">{username}</h2>
<p className="text-[#71767B] text-[15px] mb-3">@{username?.toLowerCase().replace(/\s/g, '')}</p>
{bio && <p className="text-[15px] text-[#E7E9EA] mb-3 whitespace-pre-wrap">{bio}</p>}
<div className="flex flex-wrap gap-y-1 gap-x-4 text-[#71767B] text-[15px] mb-3 items-center">
{location && <div className="flex items-center gap-1"><MapPin size={16} /> {location}</div>}
{website && (
<div className="flex items-center gap-1">
<LinkIcon size={16} />
<a href={website.startsWith('http') ? website : https://${website}} target="_blank" rel="noopener noreferrer" className="text-[#1D9BF0] hover:underline truncate max-w-[200px]">{website.replace(/^https?:\/\//, '')}</a>
</div>
)}
<div className="flex items-center gap-1"><Calendar size={16} /> Joined {user.metadata.creationTime ? new Date(user.metadata.creationTime).toLocaleDateString(undefined, { month: 'long', year: 'numeric' }) : 'recently'}</div>
</div>
<div className="flex gap-4 text-[15px]">
<span className="text-[#71767B]"><span className="font-bold text-[#E7E9EA]">{following.size}</span> Following</span>
<span className="text-[#71767B]"><span className="font-bold text-[#E7E9EA]">0</span> Followers</span>
</div>
</div>
</div>
{/* Profile Tabs */}
<div className="flex border-b border-[#2F3336] mt-2">
<div className="flex-1 h-[53px] hover:bg-[#EFF3F4]/10 transition-colors relative flex justify-center items-center cursor-pointer">
<div className="relative h-full flex items-center">
<span className="text-[15px] font-bold text-[#E7E9EA]">Posts</span>
<div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-14 h-[4px] bg-[#1D9BF0] rounded-full"></div>
</div>
</div>
<div className="flex-1 h-[53px] hover:bg-[#EFF3F4]/10 flex justify-center items-center text-[#71767B] font-bold text-[15px]">Replies</div>
<div className="flex-1 h-[53px] hover:bg-[#EFF3F4]/10 flex justify-center items-center text-[#71767B] font-bold text-[15px]">Likes</div>
</div>
{/* Own Tweets */}
{filteredTweets.length === 0 ? (
<div className="p-8 text-center text-[#71767B]">
You haven't posted anything yet.
</div>
) : (
filteredTweets.map(t => <TweetCard key={t.id} data={t} />)
)}
</div>
)}
{/* HOME / SEARCH FEED */}
{(activeTab === 'home' || activeTab === 'search') && view === 'feed' && (
<div className="animate-fade-in pb-20">
{/* Compose (Only on Home/For You) */}
{activeTab === 'home' && feedTab === 'foryou' && (
<div className="border-b border-[#2F3336] px-4 py-3 pb-2 hidden sm:block">
<div className="flex gap-3">
<div className="pt-1"><Avatar uid={user?.uid || 'anon'} username={username} /></div>
<div className="flex-1">
<textarea
value={tweetText}
onChange={(e) => { setTweetText(e.target.value); e.target.style.height = 'auto'; e.target.style.height = e.target.scrollHeight + 'px'; }}
placeholder="What is happening?!"
className="w-full bg-transparent text-[20px] text-[#E7E9EA] placeholder-[#71767B] border-none outline-none resize-none min-h-[56px] py-2 leading-7"
rows={1}
/>
<div className="flex justify-end border-t border-[#2F3336] pt-3 mt-1">
<button onClick={() => handlePost(false)} disabled={!tweetText.trim() || isSubmitting} className="bg-[#1D9BF0] hover:bg-[#1a8cd8] disabled:opacity-50 text-white font-bold px-4 py-1.5 rounded-full text-[15px]">Post</button>
</div>
</div>
</div>
</div>
)}
{/* Feed List */}
{filteredTweets.length === 0 ? (
<div className="p-10 text-center text-[#71767B]">
{feedTab === 'following' ? (
<div className="flex flex-col items-center">
<p className="text-[20px] font-bold text-white mb-2">Welcome to your timeline!</p>
<p className="max-w-xs mb-4">When you follow people, you'll see their Tweets here.</p>
<button onClick={() => setFeedTab('foryou')} className="bg-[#1D9BF0] text-white font-bold px-5 py-2 rounded-full">Find people in For You</button>
</div>
) : (
<p>No posts found.</p>
)}
</div>
) : (
filteredTweets.map(t => <TweetCard key={t.id} data={t} />)
)}
</div>
)}
{/* NOTIFICATIONS */}
{activeTab === 'notifications' && view === 'feed' && (
<div className="animate-fade-in pb-20">
{notifications.length === 0 ? (
<div className="p-12 text-center text-[#71767B]">
<p className="font-bold text-xl text-white mb-2">No notifications yet</p>
<p>When someone likes or replies, you'll see it here.</p>
</div>
) : (
notifications.map(n => <NotificationItem key={n.id} note={n} />)
)}
</div>
)}
{/* MESSAGES LIST */}
{activeTab === 'messages' && view === 'feed' && (
<div className="animate-fade-in pb-20">
{conversations.length === 0 ? (
<div className="p-12 text-center text-[#71767B]">
<div className="w-full flex justify-center mb-4"><Mail size={48} /></div>
<p className="font-bold text-xl text-white mb-2">Welcome to your inbox!</p>
<p className="max-w-xs mx-auto">Drop a line, share Tweets and more with private conversations.</p>
</div>
) : (
conversations.map(c => (
<div
key={c.uid}
onClick={() => openChat(c.uid, c.username)}
className="p-4 border-b border-[#2F3336] hover:bg-[#080808] flex gap-3 cursor-pointer"
>
<Avatar uid={c.uid} username={c.username} />
<div className="flex-1 overflow-hidden">
<div className="flex justify-between">
<span className="font-bold text-[15px]">{c.username}</span>
<span className="text-[#71767B] text-[13px]">{formatTime(c.lastMsg.createdAt)}</span>
</div>
<p className="text-[#71767B] text-[15px] truncate">
{c.lastMsg.from === user.uid ? 'You: ' : ''}{c.lastMsg.text}
</p>
</div>
</div>
))
)}
</div>
)}
{/* CHAT VIEW */}
{view === 'chat' && activeChatUser && (
<div className="flex flex-col h-[calc(100vh-53px)]">
<div className="flex-1 overflow-y-auto p-4 flex flex-col gap-3">
{currentChatMessages.map(m => {
const isMe = m.from === user.uid;
return (
<div key={m.id} className=flex ${isMe ? 'justify-end' : 'justify-start'}}>
<div className=max-w-[75%] px-4 py-2 rounded-2xl text-[15px] break-words ${isMe ? 'bg-[#1D9BF0] text-white rounded-br-none' : 'bg-[#2F3336] text-white rounded-bl-none'}}>
{m.text}
</div>
</div>
);
})}
<div ref={(el) => el?.scrollIntoView({ behavior: 'smooth' })} />
</div>
<div className="p-3 border-t border-[#2F3336] flex gap-2 items-center">
<input
className="flex-1 bg-[#202327] rounded-full px-4 py-2 focus:outline-none focus:border-[#1D9BF0] border border-transparent text-white"
placeholder="Start a new message"
value={messageText}
onChange={e => setMessageText(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleSendMessage()}
/>
<button onClick={handleSendMessage} disabled={!messageText.trim()} className="text-[#1D9BF0] p-2 hover:bg-[#1D9BF0]/10 rounded-full disabled:opacity-50">
<Send size={20} />
</button>
</div>
</div>
)}
{/* THREAD VIEW */}
{view === 'thread' && activeTweet && (
<div className="animate-slide-left pb-40">
<TweetCard data={activeTweet} isMain={true} />
<div className="border-b border-[#2F3336] px-4 py-3">
<div className="flex gap-3">
<div className="pt-1"><Avatar uid={user?.uid || 'anon'} username={username} /></div>
<div className="flex-1">
<textarea
value={tweetText}
onChange={(e) => { setTweetText(e.target.value); e.target.style.height = 'auto'; e.target.style.height = e.target.scrollHeight + 'px'; }}
placeholder="Post your reply"
className="w-full bg-transparent text-[20px] text-[#E7E9EA] placeholder-[#71767B] border-none outline-none resize-none min-h-[24px] py-2"
rows={1}
autoFocus
/>
<div className="flex justify-end pt-2">
<button onClick={() => handlePost(true)} disabled={!tweetText.trim() || isSubmitting} className="bg-[#1D9BF0] hover:bg-[#1a8cd8] disabled:opacity-50 text-white font-bold px-4 py-1.5 rounded-full text-[15px]">Reply</button>
</div>
</div>
</div>
</div>
<div>
{comments.map(c => <TweetCard key={c.id} data={c} isComment={true} />)}
{comments.length === 0 && <div className="h-32"></div>}
</div>
</div>
)}
</div>
{/* === BOTTOM NAVIGATION === */}
{view !== 'chat' && (
<nav className="h-[53px] border-t border-[#2F3336] bg-black flex justify-around items-center px-2 pb-safe z-50">
<NavItem icon={HomeIcon} active={activeTab === 'home'} onClick={() => { setActiveTab('home'); setView('feed'); }} />
<NavItem icon={Search} active={activeTab === 'search'} onClick={() => setActiveTab('search')} />
<NavItem icon={Bell} active={activeTab === 'notifications'} onClick={() => setActiveTab('notifications')} badge={notifications.filter(n => !n.read).length} />
<NavItem icon={Mail} active={activeTab === 'messages'} onClick={() => setActiveTab('messages')} />
<NavItem icon={User} active={activeTab === 'profile'} onClick={() => setActiveTab('profile')} />
</nav>
)}
</div>
{/* --- Styles --- */}
<style>{`
.custom-scrollbar::-webkit-scrollbar { width: 0px; background: transparent; }
@keyframes heartPop { 0% { transform: scale(1); } 50% { transform: scale(1.4); } 100% { transform: scale(1); } }
.animate-heart-pop { animation: heartPop 0.45s cubic-bezier(0.175, 0.885, 0.32, 1.275); }
@keyframes heartRing { 0% { transform: scale(0.5); opacity: 1; border-width: 2px; } 100% { transform: scale(2); opacity: 0; border-width: 0px; } }
.animate-heart-ring { animation: heartRing 0.45s ease-out; }
@keyframes toastUp { from { opacity: 0; transform: translate(-50%, 20px); } to { opacity: 1; transform: translate(-50%, 0); } }
.animate-toast-up { animation: toastUp 0.3s ease-out; }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.animate-fade-in { animation: fadeIn 0.2s ease-out; }
@keyframes scaleIn { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }
.animate-scale-in { animation: scaleIn 0.2s cubic-bezier(0.4, 0, 0.2, 1); }
`}</style>
</div>
);
}