import React, { useEffect, useState } from "react";
// Simple single-file Social Media app prototype // - Tailwind CSS assumed to be available in the host project // - Paste this component into a Vite/CRA React project and render <SocialApp /> in App.jsx
const samplePosts = [ { id: 1, author: "Mango Shak", avatarLetter: "M", time: "2025-09-18", text: "Hello world! This is my first post.", image: null, likes: 3, comments: [ { id: 1, author: "Ami", text: "Nice post!" }, ], }, ];
function useLocalStorage(key, initial) { const [state, setState] = useState(() => { try { const raw = localStorage.getItem(key); return raw ? JSON.parse(raw) : initial; } catch (e) { return initial; } });
useEffect(() => { try { localStorage.setItem(key, JSON.stringify(state)); } catch (e) {} }, [key, state]);
return [state, setState]; }
export default function SocialApp() { const [posts, setPosts] = useLocalStorage("sm_posts", samplePosts); const [user, setUser] = useLocalStorage("sm_user", { name: "You", avatarLetter: "Y", });
const [newText, setNewText] = useState(""); const [newImage, setNewImage] = useState(""); const [query, setQuery] = useState(""); const [filter, setFilter] = useState("latest");
function addPost() { if (!newText.trim() && !newImage.trim()) return; const id = Date.now(); const post = { id, author: user.name, avatarLetter: user.avatarLetter || user.name.charAt(0).toUpperCase(), time: new Date().toISOString(), text: newText.trim(), image: newImage.trim() || null, likes: 0, comments: [], }; setPosts([post, ...posts]); setNewText(""); setNewImage(""); }
function toggleLike(postId) { setPosts( posts.map((p) => (p.id === postId ? { ...p, likes: p.likes + 1 } : p)) ); }
function addComment(postId, text) { if (!text.trim()) return; setPosts( posts.map((p) => p.id === postId ? { ...p, comments: [ ...p.comments, { id: Date.now(), author: user.name, text: text.trim() }, ], } : p ) ); }
function deletePost(postId) { setPosts(posts.filter((p) => p.id !== postId)); }
function updateProfile(name) { setUser({ ...user, name, avatarLetter: name.charAt(0).toUpperCase() }); }
const visiblePosts = posts .filter((p) => p.text.toLowerCase().includes(query.toLowerCase())) .sort((a, b) => { if (filter === "latest") return new Date(b.time) - new Date(a.time); if (filter === "oldest") return new Date(a.time) - new Date(b.time); return 0; });
return ( <div className="min-h-screen bg-gray-50 p-4 md:p-8"> <div className="max-w-4xl mx-auto"> {/* Header */} <header className="flex items-center justify-between mb-6"> <h1 className="text-2xl font-bold">Simple Social — Prototype</h1> <div className="flex items-center gap-3"> <div className="w-10 h-10 rounded-full bg-indigo-600 text-white flex items-center justify-center font-semibold">{user.avatarLetter}</div> <div className="text-sm text-gray-700">{user.name}</div> </div> </header>
{/* Composer */}
\<section className="bg-white p-4 rounded-2xl shadow mb-6"\>
\<textarea
value={newText}
onChange={(e) =\> setNewText(e.target.value)}
placeholder={\`What's on your mind, ${user.name}?\`}
className="w-full border-0 focus:ring-0 resize-none text-sm"
rows={3}
/\>
\<div className="mt-2 flex gap-2"\>
\<input
value={newImage}
onChange={(e) =\> setNewImage(e.target.value)}
placeholder="Image URL (optional)"
className="flex-1 text-sm border rounded px-2 py-1"
/\>
\<button onClick={addPost} className="px-4 py-2 bg-indigo-600 text-white rounded"\>Post\</button\>
\</div\>
\<div className="mt-3 text-xs text-gray-500"\>Tip: paste an image URL or write a short post.\</div\>
\</section\>
{/\* Filters/Search \*/}
\<div className="flex gap-3 items-center mb-4"\>
\<input
value={query}
onChange={(e) =\> setQuery(e.target.value)}
placeholder="Search posts..."
className="flex-1 border px-3 py-2 rounded"
/\>
\<select value={filter} onChange={(e) =\> setFilter(e.target.value)} className="border rounded px-2 py-2"\>
\<option value="latest"\>Latest\</option\>
\<option value="oldest"\>Oldest\</option\>
\</select\>
\<button
onClick={() =\> {
const name = prompt("Enter display name:", user.name) || user.name;
updateProfile(name);
}}
className="px-3 py-2 border rounded"
\>
Edit Profile
\</button\>
\</div\>
{/\* Posts \*/}
\<main className="space-y-4"\>
{visiblePosts.length === 0 && (
\<div className="text-center text-gray-500 py-8"\>No posts yet — be the first!\</div\>
)}
{visiblePosts.map((post) =\> (
\<article key={post.id} className="bg-white p-4 rounded-2xl shadow"\>
\<div className="flex gap-3"\>
\<div className="w-12 h-12 rounded-full bg-indigo-600 text-white flex items-center justify-center font-semibold"\>{post.avatarLetter || post.author.charAt(0)}\</div\>
\<div className="flex-1"\>
\<div className="flex items-center justify-between"\>
\<div\>
\<div className="font-semibold"\>{post.author}\</div\>
\<div className="text-xs text-gray-500"\>{new Date(post.time).toLocaleString()}\</div\>
\</div\>
\<div className="text-xs"\>
\<button onClick={() =\> deletePost(post.id)} className="px-2 py-1 rounded hover:bg-gray-100"\>Delete\</button\>
\</div\>
\</div\>
\<p className="mt-3 whitespace-pre-wrap"\>{post.text}\</p\>
{post.image && (
\<div className="mt-3"\>
\<img src={post.image} alt="post" className="w-full max-h-64 object-cover rounded-lg" /\>
\</div\>
)}
\<div className="mt-3 flex items-center gap-3 text-sm text-gray-600"\>
\<button onClick={() =\> toggleLike(post.id)} className="px-2 py-1 rounded hover:bg-gray-100"\>👍 Like ({post.likes})\</button\>
\<CommentBox post={post} onAddComment={(t) =\> addComment(post.id, t)} /\>
\</div\>
{post.comments.length \> 0 && (
\<div className="mt-3 border-t pt-3"\>
{post.comments.map((c) =\> (
\<div key={c.id} className="text-sm mb-2"\>
\<span className="font-semibold"\>{c.author}\</span\>: {c.text}
\</div\>
))}
\</div\>
)}
\</div\>
\</div\>
\</article\>
))}
\</main\>
{/\* Footer / small profile settings \*/}
\<footer className="mt-8 text-center text-gray-500 text-xs"\>Prototype — data saved locally in your browser.\</footer\>
</div>
</div>
); }
function CommentBox({ post, onAddComment }) { const [open, setOpen] = useState(false); const [text, setText] = useState("");
return ( <div> <button onClick={() => setOpen((s) => !s)} className="px-2 py-1 rounded hover:bg-gray-100">💬 Comment</button> {open && ( <div className="mt-2 flex gap-2"> <input value={text} onChange={(e) => setText(e.target.value)} placeholder="Write a comment..." className="flex-1 border rounded px-2 py-1 text-sm" /> <button onClicimport React, { useEffect, useState } from "react";
// Simple single-file Social Media app prototype // - Tailwind CSS assumed to be available in the host project // - Paste this component into a Vite/CRA React project and render <SocialApp /> in App.jsx
const samplePosts = [ { id: 1, author: "Mango Shak", avatarLetter: "M", time: "2025-09-18", text: "Hello world! This is my first post.", image: null, likes: 3, comments: [ { id: 1, author: "Ami", text: "Nice post!" }, ], }, ];
function useLocalStorage(key, initial) { const [state, setState] = useState(() => { try { const raw = localStorage.getItem(key); return raw ? JSON.parse(raw) : initial; } catch (e) { return initial; } });
useEffect(() => { try { localStorage.setItem(key, JSON.stringify(state)); } catch (e) {} }, [key, state]);
return [state, setState]; }
export default function SocialApp() { const [posts, setPosts] = useLocalStorage("sm_posts", samplePosts); const [user, setUser] = useLocalStorage("sm_user", { name: "You", avatarLetter: "Y", });
const [newText, setNewText] = useState(""); const [newImage, setNewImage] = useState(""); const [query, setQuery] = useState(""); const [filter, setFilter] = useState("latest");
function addPost() { if (!newText.trim() && !newImage.trim()) return; const id = Date.now(); const post = { id, author: user.name, avatarLetter: user.avatarLetter || user.name.charAt(0).toUpperCase(), time: new Date().toISOString(), text: newText.trim(), image: newImage.trim() || null, likes: 0, comments: [], }; setPosts([post, ...posts]); setNewText(""); setNewImage(""); }
function toggleLike(postId) { setPosts( posts.map((p) => (p.id === postId ? { ...p, likes: p.likes + 1 } : p)) ); }
function addComment(postId, text) { if (!text.trim()) return; setPosts( posts.map((p) => p.id === postId ? { ...p, comments: [ ...p.comments, { id: Date.now(), author: user.name, text: text.trim() }, ], } : p ) ); }
function deletePost(postId) { setPosts(posts.filter((p) => p.id !== postId)); }
function updateProfile(name) { setUser({ ...user, name, avatarLetter: name.charAt(0).toUpperCase() }); }
const visiblePosts = posts .filter((p) => p.text.toLowerCase().includes(query.toLowerCase())) .sort((a, b) => { if (filter === "latest") return new Date(b.time) - new Date(a.time); if (filter === "oldest") return new Date(a.time) - new Date(b.time); return 0; });
return ( <div className="min-h-screen bg-gray-50 p-4 md:p-8"> <div className="max-w-4xl mx-auto"> {/* Header */} <header className="flex items-center justify-between mb-6"> <h1 className="text-2xl font-bold">Simple Social — Prototype</h1> <div className="flex items-center gap-3"> <div className="w-10 h-10 rounded-full bg-indigo-600 text-white flex items-center justify-center font-semibold">{user.avatarLetter}</div> <div className="text-sm text-gray-700">{user.name}</div> </div> </header>
{/* Composer */}
\<section className="bg-white p-4 rounded-2xl shadow mb-6"\>
\<textarea
value={newText}
onChange={(e) =\> setNewText(e.target.value)}
placeholder={\`What's on your mind, ${user.name}?\`}
className="w-full border-0 focus:ring-0 resize-none text-sm"
rows={3}
/\>
\<div className="mt-2 flex gap-2"\>
\<input
value={newImage}
onChange={(e) =\> setNewImage(e.target.value)}
placeholder="Image URL (optional)"
className="flex-1 text-sm border rounded px-2 py-1"
/\>
\<button onClick={addPost} className="px-4 py-2 bg-indigo-600 text-white rounded"\>Post\</button\>
\</div\>
\<div className="mt-3 text-xs text-gray-500"\>Tip: paste an image URL or write a short post.\</div\>
\</section\>
{/\* Filters/Search \*/}
\<div className="flex gap-3 items-center mb-4"\>
\<input
value={query}
onChange={(e) =\> setQuery(e.target.value)}
placeholder="Search posts..."
className="flex-1 border px-3 py-2 rounded"
/\>
\<select value={filter} onChange={(e) =\> setFilter(e.target.value)} className="border rounded px-2 py-2"\>
\<option value="latest"\>Latest\</option\>
\<option value="oldest"\>Oldest\</option\>
\</select\>
\<button
onClick={() =\> {
const name = prompt("Enter display name:", user.name) || user.name;
updateProfile(name);
}}
className="px-3 py-2 border rounded"
\>
Edit Profile
\</button\>
\</div\>
{/\* Posts \*/}
\<main className="space-y-4"\>
{visiblePosts.length === 0 && (
\<div className="text-center text-gray-500 py-8"\>No posts yet — be the first!\</div\>
)}
{visiblePosts.map((post) =\> (
\<article key={post.id} className="bg-white p-4 rounded-2xl shadow"\>
\<div className="flex gap-3"\>
\<div className="w-12 h-12 rounded-full bg-indigo-600 text-white flex items-center justify-center font-semibold"\>{post.avatarLetter || post.author.charAt(0)}\</div\>
\<div className="flex-1"\>
\<div className="flex items-center justify-between"\>
\<div\>
\<div className="font-semibold"\>{post.author}\</div\>
\<div className="text-xs text-gray-500"\>{new Date(post.time).toLocaleString()}\</div\>
\</div\>
\<div className="text-xs"\>
\<button onClick={() =\> deletePost(post.id)} className="px-2 py-1 rounded hover:bg-gray-100"\>Delete\</button\>
\</div\>
\</div\>
\<p className="mt-3 whitespace-pre-wrap"\>{post.text}\</p\>
{post.image && (
\<div className="mt-3"\>
\<img src={post.image} alt="post" className="w-full max-h-64 object-cover rounded-lg" /\>
\</div\>
)}
\<div className="mt-3 flex items-center gap-3 text-sm text-gray-600"\>
\<button onClick={() =\> toggleLike(post.id)} className="px-2 py-1 rounded hover:bg-gray-100"\>👍 Like ({post.likes})\</button\>
\<CommentBox post={post} onAddComment={(t) =\> addComment(post.id, t)} /\>
\</div\>
{post.comments.length \> 0 && (
\<div className="mt-3 border-t pt-3"\>
{post.comments.map((c) =\> (
\<div key={c.id} className="text-sm mb-2"\>
\<span className="font-semibold"\>{c.author}\</span\>: {c.text}
\</div\>
))}
\</div\>
)}
\</div\>
\</div\>
\</article\>
))}
\</main\>
{/\* Footer / small profile settings \*/}
\<footer className="mt-8 text-center text-gray-500 text-xs"\>Prototype — data saved locally in your browser.\</footer\>
</div>
</div>
); }
function CommentBox({ post, onAddComment }) { const [open, setOpen] = useState(false); const [text, setText] = useState("");
return ( <div> <button onClick={() => setOpen((s) => !s)} className="px-2 py-1 rounded hover:bg-gray-100">💬 Comment</button> {open && ( <div className="mt-2 flex gap-2"> <input value={text} onChange={(e) => setText(e.target.value)} placeholder="Write a comment..." className="flex-1 border rounded px-2 py-1 text-sm" /> <button onClick={() => { onAddComment(text); setText(""); }} className="px-3 py-1 border rounded text-sm" > Send </button> </div> )} </div> ); }
k={() => { onAddComment(text); setText(""); }} className="px-3 py-1 border rounded text-sm" > Send </button> </div> )} </div> ); }