Reddit ma communities (subreddity) - grupy tematyczne. Piraci będą mieć swoje communities jak r/TreasureHunting, r/SeaBattles, r/PirateLife.
1interface Community {
2 id: string; // np. "treasure-hunting"
3 name: string; // np. "r/TreasureHunting"
4 displayName: string; // np. "Treasure Hunting"
5 description: string;
6 icon: string; // Emoji lub URL
7 members: number;
8 color: string; // Kolor motywu
9 rules: string[];
10}1const communities: Community[] = [
2 {
3 id: 'treasure-hunting',
4 name: 'r/TreasureHunting',
5 displayName: 'Treasure Hunting',
6 description: 'Odkrycia, mapy i skarby ze wszystkich mórz',
7 icon: '💎',
8 members: 12450,
9 color: '#FFD700',
10 rules: [
11 'Dziel się prawdziwymi znaleziskami',
12 'Szanuj innych poszukiwaczy',
13 'Nie ujawniaj lokalizacji aktywnych wypraw',
14 ],
15 },
16 {
17 id: 'sea-battles',
18 name: 'r/SeaBattles',
19 displayName: 'Sea Battles',
20 description: 'Bitwy morskie, strategie i taktyki',
21 icon: '⚔️',
22 members: 8920,
23 color: '#DC2626',
24 rules: [
25 'Opisuj bitwy szczegółowo',
26 'Fair play w dyskusjach',
27 'Brak gloryfikacji przemocy',
28 ],
29 },
30 {
31 id: 'pirate-life',
32 name: 'r/PirateLife',
33 displayName: 'Pirate Life',
34 description: 'Codzienne życie na morzu i przygody',
35 icon: '🏴☠️',
36 members: 25670,
37 color: '#000000',
38 rules: [
39 'Bądź autentyczny',
40 'Dziel się historiami',
41 'Szanuj kodeks piracki',
42 ],
43 },
44 {
45 id: 'ship-building',
46 name: 'r/ShipBuilding',
47 displayName: 'Ship Building',
48 description: 'Budowa i naprawa statków',
49 icon: '⚓',
50 members: 5430,
51 color: '#1E40AF',
52 rules: [
53 'Dziel się planami i schematami',
54 'Pomóż początkującym',
55 'Bezpieczeństwo przede wszystkim',
56 ],
57 },
58];1'use client';
2
3import { useState } from 'react';
4
5export default function Feed() {
6 const [posts, setPosts] = useState<Post[]>(allPosts);
7 const [selectedCommunity, setSelectedCommunity] = useState<string | null>(null);
8
9 const filteredPosts = selectedCommunity
10 ? posts.filter(post => post.community === selectedCommunity)
11 : posts;
12
13 return (
14 <div className="flex gap-4">
15 {/* Sidebar - Communities */}
16 <aside className="w-64">
17 <div className="bg-white rounded-lg shadow p-4">
18 <h2 className="font-bold text-lg mb-3">Communities</h2>
19
20 <button
21 onClick={() => setSelectedCommunity(null)}
22 className={`w-full text-left px-3 py-2 rounded mb-1 ${
23 selectedCommunity === null
24 ? 'bg-gray-100 font-medium'
25 : 'hover:bg-gray-50'
26 }`}
27 >
28 🏴☠️ All Communities
29 </button>
30
31 {communities.map(community => (
32 <button
33 key={community.id}
34 onClick={() => setSelectedCommunity(community.id)}
35 className={`w-full text-left px-3 py-2 rounded mb-1 flex items-center gap-2 ${
36 selectedCommunity === community.id
37 ? 'bg-gray-100 font-medium'
38 : 'hover:bg-gray-50'
39 }`}
40 >
41 <span className="text-xl">{community.icon}</span>
42 <div className="flex-1">
43 <div className="text-sm font-medium">{community.name}</div>
44 <div className="text-xs text-gray-500">
45 {formatMembers(community.members)} members
46 </div>
47 </div>
48 </button>
49 ))}
50 </div>
51 </aside>
52
53 {/* Main Feed */}
54 <main className="flex-1">
55 {selectedCommunity && (
56 <CommunityHeader
57 community={communities.find(c => c.id === selectedCommunity)!}
58 />
59 )}
60
61 <div className="space-y-3">
62 {filteredPosts.map(post => (
63 <PostCard key={post.id} post={post} />
64 ))}
65 </div>
66 </main>
67 </div>
68 );
69}
70
71function formatMembers(count: number): string {
72 if (count >= 1000000) return `${(count / 1000000).toFixed(1)}M`;
73 if (count >= 1000) return `${(count / 1000).toFixed(1)}K`;
74 return count.toString();
75}1interface CommunityHeaderProps {
2 community: Community;
3}
4
5export default function CommunityHeader({ community }: CommunityHeaderProps) {
6 const [isJoined, setIsJoined] = useState(false);
7
8 return (
9 <div
10 className="rounded-lg mb-4 overflow-hidden"
11 style={{ backgroundColor: community.color }}
12 >
13 {/* Banner */}
14 <div className="h-24 bg-gradient-to-r from-black/20 to-transparent" />
15
16 {/* Info */}
17 <div className="bg-white p-4">
18 <div className="flex items-start gap-4">
19 {/* Icon */}
20 <div className="text-6xl -mt-12 bg-white rounded-full p-2 shadow-lg">
21 {community.icon}
22 </div>
23
24 <div className="flex-1">
25 <h1 className="text-2xl font-bold">{community.displayName}</h1>
26 <p className="text-gray-600 text-sm mb-3">
27 {community.name} • {formatMembers(community.members)} members
28 </p>
29 <p className="text-gray-700 mb-4">{community.description}</p>
30
31 <button
32 onClick={() => setIsJoined(!isJoined)}
33 className={`px-6 py-2 rounded-full font-bold transition ${
34 isJoined
35 ? 'bg-gray-200 text-gray-700 hover:bg-gray-300'
36 : 'bg-blue-500 text-white hover:bg-blue-600'
37 }`}
38 >
39 {isJoined ? 'Joined' : 'Join'}
40 </button>
41 </div>
42 </div>
43
44 {/* Rules */}
45 <div className="mt-6 bg-gray-50 rounded-lg p-4">
46 <h3 className="font-bold mb-2">Community Rules</h3>
47 <ol className="list-decimal list-inside space-y-1 text-sm text-gray-700">
48 {community.rules.map((rule, index) => (
49 <li key={index}>{rule}</li>
50 ))}
51 </ol>
52 </div>
53 </div>
54 </div>
55 );
56}1'use client';
2
3import { useState } from 'react';
4
5export default function CreatePost() {
6 const [selectedCommunity, setSelectedCommunity] = useState('');
7 const [title, setTitle] = useState('');
8 const [content, setContent] = useState('');
9
10 const handleSubmit = (e: React.FormEvent) => {
11 e.preventDefault();
12
13 const newPost: Post = {
14 id: Date.now(),
15 title,
16 content,
17 author: 'You',
18 community: selectedCommunity,
19 upvotes: 1,
20 downvotes: 0,
21 userVote: null,
22 comments: 0,
23 timestamp: new Date(),
24 };
25
26 // Dodaj post...
27 };
28
29 return (
30 <div className="bg-white rounded-lg shadow p-6">
31 <h2 className="text-xl font-bold mb-4">Create a Post</h2>
32
33 <form onSubmit={handleSubmit} className="space-y-4">
34 {/* Community Selector */}
35 <div>
36 <label className="block font-medium mb-2">Choose a community</label>
37 <select
38 value={selectedCommunity}
39 onChange={(e) => setSelectedCommunity(e.target.value)}
40 className="w-full p-3 border rounded-lg"
41 required
42 >
43 <option value="">Select community...</option>
44 {communities.map(community => (
45 <option key={community.id} value={community.id}>
46 {community.icon} {community.name}
47 </option>
48 ))}
49 </select>
50 </div>
51
52 {/* Title */}
53 <div>
54 <label className="block font-medium mb-2">Title</label>
55 <input
56 type="text"
57 value={title}
58 onChange={(e) => setTitle(e.target.value)}
59 placeholder="Ciekawy tytuł twojego posta..."
60 className="w-full p-3 border rounded-lg"
61 maxLength={300}
62 required
63 />
64 <p className="text-xs text-gray-500 mt-1">
65 {title.length}/300 characters
66 </p>
67 </div>
68
69 {/* Content */}
70 <div>
71 <label className="block font-medium mb-2">Text (optional)</label>
72 <textarea
73 value={content}
74 onChange={(e) => setContent(e.target.value)}
75 placeholder="Podziel się swoimi przemyśleniami..."
76 className="w-full p-3 border rounded-lg resize-none"
77 rows={8}
78 />
79 </div>
80
81 {/* Submit */}
82 <div className="flex gap-2">
83 <button
84 type="submit"
85 disabled={!selectedCommunity || !title}
86 className="flex-1 py-3 bg-blue-500 text-white rounded-full font-bold hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed"
87 >
88 Post
89 </button>
90 <button
91 type="button"
92 className="px-6 py-3 border rounded-full font-bold hover:bg-gray-50"
93 >
94 Save Draft
95 </button>
96 </div>
97 </form>
98 </div>
99 );
100}1'use client';
2
3import { useState } from 'react';
4
5export default function SearchBar() {
6 const [searchQuery, setSearchQuery] = useState('');
7 const [searchIn, setSearchIn] = useState<'posts' | 'communities'>('posts');
8
9 const handleSearch = (e: React.FormEvent) => {
10 e.preventDefault();
11 // Implementacja wyszukiwania...
12 };
13
14 return (
15 <form onSubmit={handleSearch} className="flex gap-2">
16 <div className="flex-1 relative">
17 <input
18 type="text"
19 value={searchQuery}
20 onChange={(e) => setSearchQuery(e.target.value)}
21 placeholder="Szukaj postów i społeczności..."
22 className="w-full pl-10 pr-4 py-2 border rounded-full focus:ring-2 focus:ring-blue-500 outline-none"
23 />
24 <span className="absolute left-3 top-2.5 text-gray-400">🔍</span>
25 </div>
26
27 <select
28 value={searchIn}
29 onChange={(e) => setSearchIn(e.target.value as 'posts' | 'communities')}
30 className="px-4 py-2 border rounded-full"
31 >
32 <option value="posts">Posts</option>
33 <option value="communities">Communities</option>
34 </select>
35
36 <button
37 type="submit"
38 className="px-6 py-2 bg-blue-500 text-white rounded-full font-bold hover:bg-blue-600"
39 >
40 Search
41 </button>
42 </form>
43 );
44}✅ Communities - struktura subredditów ✅ Filtering - filtrowanie postów według community ✅ Community header - banner z info i rules ✅ Join/Leave - członkostwo w community ✅ Create post - wybór community przy tworzeniu ✅ Sidebar - lista communities z liczbą członków ✅ Search - wyszukiwanie postów i communities ✅ Color themes - każdy community ma swój kolor
Masz teraz kompletny Reddit clone z voting, threaded comments i communities! 🎉
Do zobaczenia! 🚀