Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie
CodeWorlds

Communities i Filtering - Subreddity

Reddit ma communities (subreddity) - grupy tematyczne. Piraci będą mieć swoje communities jak r/TreasureHunting, r/SeaBattles, r/PirateLife.

Community Structure

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}

Communities List

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];

Filtering Posts by Community

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}

Community Header/Banner

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}

Create Post in Community

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}

Search & Filter

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}

Podsumowanie 🎓

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! 🚀

Przejdź do CodeWorlds