Czas dodać zaawansowane funkcje jak komentarze, lajkowanie i upload zdjęć (symulowany).
Instagram ma wiele stanów działających razem:
1interface Comment {
2 id: number;
3 author: string;
4 avatar: string;
5 text: string;
6 timestamp: Date;
7}
8
9interface Post {
10 id: number;
11 imageUrl: string;
12 caption: string;
13 author: string;
14 avatar: string;
15 likes: number;
16 isLiked: boolean;
17 comments: Comment[];
18 timestamp: Date;
19}1'use client';
2
3import { useState } from 'react';
4import Image from 'next/image';
5
6interface PostProps {
7 post: Post;
8 onLike: (postId: number) => void;
9 onAddComment: (postId: number, comment: string) => void;
10}
11
12export default function InstagramPost({ post, onLike, onAddComment }: PostProps) {
13 const [commentText, setCommentText] = useState('');
14 const [showAllComments, setShowAllComments] = useState(false);
15
16 const handleSubmitComment = (e: React.FormEvent) => {
17 e.preventDefault();
18 if (commentText.trim()) {
19 onAddComment(post.id, commentText);
20 setCommentText('');
21 }
22 };
23
24 const displayedComments = showAllComments
25 ? post.comments
26 : post.comments.slice(0, 2);
27
28 return (
29 <div className="bg-white border rounded-lg mb-4 max-w-2xl mx-auto">
30 {/* Header */}
31 <div className="flex items-center gap-3 p-4">
32 <div className="text-2xl">{post.avatar}</div>
33 <div className="flex-1">
34 <h3 className="font-bold text-sm">{post.author}</h3>
35 <p className="text-xs text-gray-500">
36 {post.timestamp.toLocaleDateString('pl-PL')}
37 </p>
38 </div>
39 <button className="text-gray-600 hover:text-gray-800">•••</button>
40 </div>
41
42 {/* Image */}
43 <div className="relative aspect-square">
44 <Image
45 src={post.imageUrl}
46 alt={post.caption}
47 fill
48 className="object-cover"
49 />
50 </div>
51
52 {/* Actions */}
53 <div className="p-4">
54 <div className="flex items-center gap-4 mb-3">
55 <button
56 onClick={() => onLike(post.id)}
57 className={`text-2xl transition-transform hover:scale-110 ${
58 post.isLiked ? 'text-red-500' : 'text-gray-700'
59 }`}
60 >
61 {post.isLiked ? '❤️' : '🤍'}
62 </button>
63 <button className="text-2xl hover:scale-110 transition-transform">
64 💬
65 </button>
66 <button className="text-2xl hover:scale-110 transition-transform">
67 📤
68 </button>
69 <button className="ml-auto text-2xl hover:scale-110 transition-transform">
70 🔖
71 </button>
72 </div>
73
74 {/* Likes count */}
75 <p className="font-bold text-sm mb-2">{post.likes} polubień</p>
76
77 {/* Caption */}
78 <p className="text-sm mb-2">
79 <span className="font-bold">{post.author}</span> {post.caption}
80 </p>
81
82 {/* Comments */}
83 <div className="space-y-2">
84 {post.comments.length > 2 && !showAllComments && (
85 <button
86 onClick={() => setShowAllComments(true)}
87 className="text-sm text-gray-500 hover:text-gray-700"
88 >
89 Wyświetl wszystkie komentarze ({post.comments.length})
90 </button>
91 )}
92
93 {displayedComments.map((comment) => (
94 <div key={comment.id} className="text-sm">
95 <span className="font-bold">{comment.author}</span>{' '}
96 {comment.text}
97 </div>
98 ))}
99 </div>
100
101 {/* Add Comment */}
102 <form onSubmit={handleSubmitComment} className="mt-3 flex items-center gap-2 border-t pt-3">
103 <input
104 type="text"
105 value={commentText}
106 onChange={(e) => setCommentText(e.target.value)}
107 placeholder="Dodaj komentarz..."
108 className="flex-1 outline-none text-sm"
109 />
110 <button
111 type="submit"
112 disabled={!commentText.trim()}
113 className="text-blue-500 font-bold text-sm disabled:text-blue-300 disabled:cursor-not-allowed"
114 >
115 Opublikuj
116 </button>
117 </form>
118 </div>
119 </div>
120 );
121}1'use client';
2
3import { useState } from 'react';
4import InstagramPost from './InstagramPost';
5
6export default function TreasureFeed() {
7 const [posts, setPosts] = useState<Post[]>([
8 {
9 id: 1,
10 imageUrl: '/treasure-1.jpg',
11 caption: 'Znalazłem ogromny skarb na wyspie! 💰',
12 author: 'Kapitan Redbeard',
13 avatar: '🏴☠️',
14 likes: 42,
15 isLiked: false,
16 comments: [
17 {
18 id: 1,
19 author: 'Morgan',
20 avatar: '⚓',
21 text: 'Niesamowite! Gdzie to znalazłeś?',
22 timestamp: new Date(),
23 },
24 ],
25 timestamp: new Date(),
26 },
27 ]);
28
29 const handleLike = (postId: number) => {
30 setPosts(posts.map(post =>
31 post.id === postId
32 ? {
33 ...post,
34 likes: post.isLiked ? post.likes - 1 : post.likes + 1,
35 isLiked: !post.isLiked,
36 }
37 : post
38 ));
39 };
40
41 const handleAddComment = (postId: number, commentText: string) => {
42 setPosts(posts.map(post =>
43 post.id === postId
44 ? {
45 ...post,
46 comments: [
47 ...post.comments,
48 {
49 id: Date.now(),
50 author: 'Ty',
51 avatar: '👤',
52 text: commentText,
53 timestamp: new Date(),
54 },
55 ],
56 }
57 : post
58 ));
59 };
60
61 return (
62 <div className="min-h-screen bg-gray-50 py-8">
63 <div className="max-w-2xl mx-auto">
64 <h1 className="text-2xl font-bold mb-6 px-4">⚓ Treasure Feed</h1>
65
66 {posts.map(post => (
67 <InstagramPost
68 key={post.id}
69 post={post}
70 onLike={handleLike}
71 onAddComment={handleAddComment}
72 />
73 ))}
74 </div>
75 </div>
76 );
77}1'use client';
2
3import { useState } from 'react';
4
5interface UploadFormProps {
6 onUpload: (caption: string, imageUrl: string) => void;
7}
8
9export default function UploadPhotoForm({ onUpload }: UploadFormProps) {
10 const [caption, setCaption] = useState('');
11 const [previewUrl, setPreviewUrl] = useState('');
12 const [isOpen, setIsOpen] = useState(false);
13
14 // Symulacja uploadu - w prawdziwej apce używałbyś FileReader lub upload do serwera
15 const handleImageSelect = () => {
16 // Dla demo użyjemy losowego obrazu z Unsplash
17 const randomId = Math.floor(Math.random() * 1000);
18 setPreviewUrl(`https://picsum.photos/seed/${randomId}/600/600`);
19 };
20
21 const handleSubmit = (e: React.FormEvent) => {
22 e.preventDefault();
23 if (caption && previewUrl) {
24 onUpload(caption, previewUrl);
25 setCaption('');
26 setPreviewUrl('');
27 setIsOpen(false);
28 }
29 };
30
31 return (
32 <>
33 <button
34 onClick={() => setIsOpen(true)}
35 className="fixed bottom-8 right-8 bg-blue-600 text-white w-16 h-16 rounded-full shadow-lg hover:bg-blue-700 transition-colors text-2xl"
36 >
37 +
38 </button>
39
40 {isOpen && (
41 <div className="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4">
42 <div className="bg-white rounded-lg max-w-md w-full p-6">
43 <h2 className="text-xl font-bold mb-4">Dodaj nowe zdjęcie 📸</h2>
44
45 <form onSubmit={handleSubmit} className="space-y-4">
46 {!previewUrl ? (
47 <button
48 type="button"
49 onClick={handleImageSelect}
50 className="w-full h-64 border-2 border-dashed border-gray-300 rounded-lg flex items-center justify-center hover:border-blue-500 transition-colors"
51 >
52 <div className="text-center">
53 <div className="text-4xl mb-2">📷</div>
54 <p className="text-gray-600">Kliknij aby wybrać zdjęcie</p>
55 </div>
56 </button>
57 ) : (
58 <div className="relative aspect-square rounded-lg overflow-hidden">
59 <img src={previewUrl} alt="Preview" className="w-full h-full object-cover" />
60 <button
61 type="button"
62 onClick={() => setPreviewUrl('')}
63 className="absolute top-2 right-2 bg-white rounded-full w-8 h-8 flex items-center justify-center shadow-lg"
64 >
65 ✕
66 </button>
67 </div>
68 )}
69
70 <textarea
71 value={caption}
72 onChange={(e) => setCaption(e.target.value)}
73 placeholder="Napisz podpis..."
74 className="w-full p-3 border rounded-lg resize-none outline-none focus:ring-2 focus:ring-blue-500"
75 rows={3}
76 />
77
78 <div className="flex gap-2">
79 <button
80 type="button"
81 onClick={() => {
82 setIsOpen(false);
83 setPreviewUrl('');
84 setCaption('');
85 }}
86 className="flex-1 px-4 py-2 border rounded-lg hover:bg-gray-50"
87 >
88 Anuluj
89 </button>
90 <button
91 type="submit"
92 disabled={!caption || !previewUrl}
93 className="flex-1 px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 disabled:bg-gray-300 disabled:cursor-not-allowed"
94 >
95 Opublikuj
96 </button>
97 </div>
98 </form>
99 </div>
100 </div>
101 )}
102 </>
103 );
104}✅ Multiple state - zarządzanie wieloma stanami (posts, likes, comments) ✅ Nested updates - aktualizacja obiektów w tablicy ✅ Controlled inputs - value + onChange ✅ Conditional rendering - pokazywanie komentarzy ✅ Event delegation - onLike, onAddComment jako props ✅ Optimistic UI - natychmiastowa aktualizacja UI ✅ Form handling - upload form z preview ✅ Modal patterns - floating action button + modal
Masz teraz kompletny Instagram clone z komentarzami i lajkami! 🎉
Do zobaczenia! 🚀