Merge Featured and All sections on discovery pages
Campaigns, Groups, and Pledges each previously stacked a Featured shelf above an All-X section. Collapse them into a single section titled simply 'Campaigns' / 'Groups' / 'Pledges' that: - Idle (no query, no sort, no country) shows the moderator-featured grid. If nothing is featured yet, falls back to the chronological all-X grid so the page is never blank. - Active (the user typed, picked Top/New, or chose a country) shows the full result set, ranked or chronological per the toolbar. The shared toolbar drops the 'default' sort option from its dropdown (now only Top and New). Clicking an already-active sort returns the page to the curated idle view, giving users a clear exit affordance now that 'default' is no longer an explicit menu choice. Personal shelves (My pledges / My groups / Your campaigns) stay above the unified section as separate, user-scoped lists.
This commit is contained in:
@@ -133,7 +133,14 @@ export function DiscoverySearchToolbar({
|
||||
key={value}
|
||||
checked={sort === value}
|
||||
onCheckedChange={(checked) => {
|
||||
// `checked === false` means the user clicked the
|
||||
// currently-active item — return to the curated
|
||||
// `default` view (featured-first) rather than leaving
|
||||
// them stuck on Top/New with no exit affordance now
|
||||
// that `default` is no longer an exposed option in the
|
||||
// dropdown.
|
||||
if (checked) onSortChange(value);
|
||||
else onSortChange('default');
|
||||
}}
|
||||
// The checkbox slot on the left is hidden in favour of an
|
||||
// explicit `Check` on the right (matches the
|
||||
|
||||
+6
-6
@@ -216,8 +216,8 @@
|
||||
"myPledgesTagline": "التعهدات التي أنشأتها.",
|
||||
"featuredPledges": "تعهدات مميزة",
|
||||
"featuredPledgesTagline": "تعهدات يسلّط فريق {{appName}} الضوء عليها.",
|
||||
"allPledges": "كل التعهدات",
|
||||
"allPledgesTagline": "تصفّح كل تعهد على الشبكة.",
|
||||
"allPledges": "التعهدات",
|
||||
"allPledgesTagline": "مختارة من قِبل المشرفين. ابحث أو رتّب لتصفّح كل تعهد.",
|
||||
"sectionActive": "التعهدات النشطة",
|
||||
"sectionUpcoming": "التعهدات القادمة",
|
||||
"sectionPast": "التعهدات السابقة",
|
||||
@@ -347,8 +347,8 @@
|
||||
"myGroupsTagline": "المجموعات التي أسستها أو تشرف عليها أو تتابعها.",
|
||||
"featuredGroups": "المجموعات المميزة",
|
||||
"featuredGroupsTagline": "مجموعات بارزة تستحق اهتمامك.",
|
||||
"allGroups": "كل المجموعات",
|
||||
"allGroupsTagline": "تصفّح المجموعات أو ابحث.",
|
||||
"allGroups": "المجموعات",
|
||||
"allGroupsTagline": "مختارة من قِبل المشرفين. ابحث أو رتّب لتصفّح كل مجموعة.",
|
||||
"loginToSeeTitle": "سجّل الدخول لرؤية مجموعاتك",
|
||||
"loginToSeeBody": "ستظهر هنا المجموعات التي أسستها أو التي تشرف عليها.",
|
||||
"noGroupsTitle": "لا توجد مجموعات بعد",
|
||||
@@ -704,10 +704,10 @@
|
||||
"emptyHint": "كن أول من يبدأ حملة تمويل على {{appName}}. اروِ قصتك، اختر المستفيدين، وشارك الرابط."
|
||||
},
|
||||
"all": {
|
||||
"title": "كل الحملات",
|
||||
"title": "الحملات",
|
||||
"seoTitle": "كل الحملات",
|
||||
"description": "تصفّح كل الحملات المنشورة على Agora.",
|
||||
"sectionTagline": "تصفّح كل قضيّة على الشبكة.",
|
||||
"sectionTagline": "مختارة من قِبل المشرفين. ابحث أو رتّب لتصفّح الشبكة بأكملها.",
|
||||
"heroKicker": "الحملات",
|
||||
"heroHeading": "كل قضيّة،",
|
||||
"heroHeadingLine2": "في مكان واحد.",
|
||||
|
||||
+6
-6
@@ -644,8 +644,8 @@
|
||||
"myPledgesTagline": "Pledges you've created.",
|
||||
"featuredPledges": "Featured pledges",
|
||||
"featuredPledgesTagline": "Pledges highlighted by the {{appName}} team.",
|
||||
"allPledges": "All pledges",
|
||||
"allPledgesTagline": "Browse every pledge on the network.",
|
||||
"allPledges": "Pledges",
|
||||
"allPledgesTagline": "Highlighted by moderators. Search or sort to browse every pledge.",
|
||||
"sectionActive": "Active pledges",
|
||||
"sectionUpcoming": "Upcoming pledges",
|
||||
"sectionPast": "Past pledges",
|
||||
@@ -779,8 +779,8 @@
|
||||
"myGroupsTagline": "Groups you've founded, moderate, or follow.",
|
||||
"featuredGroups": "Featured groups",
|
||||
"featuredGroupsTagline": "Standout groups worth your attention.",
|
||||
"allGroups": "All groups",
|
||||
"allGroupsTagline": "Browse groups or search.",
|
||||
"allGroups": "Groups",
|
||||
"allGroupsTagline": "Highlighted by moderators. Search or sort to browse every group.",
|
||||
"searchPlaceholder": "Search groups\u2026",
|
||||
"searchAriaLabel": "Search groups",
|
||||
"noMatch": "No groups match \u201c{{query}}\u201d",
|
||||
@@ -1144,10 +1144,10 @@
|
||||
"emptyHint": "Be the first to start a fundraiser on {{appName}}. Tell your story, choose your beneficiaries, and share the link."
|
||||
},
|
||||
"all": {
|
||||
"title": "All Campaigns",
|
||||
"title": "Campaigns",
|
||||
"seoTitle": "All campaigns",
|
||||
"description": "Browse every campaign published on Agora.",
|
||||
"sectionTagline": "Browse every cause on the network.",
|
||||
"sectionTagline": "Highlighted by moderators. Search or sort to browse the full network.",
|
||||
"heroKicker": "Campaigns",
|
||||
"heroHeading": "Every cause,",
|
||||
"heroHeadingLine2": "in one place.",
|
||||
|
||||
+6
-6
@@ -220,8 +220,8 @@
|
||||
"myPledgesTagline": "Promesas que has creado.",
|
||||
"featuredPledges": "Promesas destacadas",
|
||||
"featuredPledgesTagline": "Promesas destacadas por el equipo de {{appName}}.",
|
||||
"allPledges": "Todas las promesas",
|
||||
"allPledgesTagline": "Explora todas las promesas de la red.",
|
||||
"allPledges": "Promesas",
|
||||
"allPledgesTagline": "Destacadas por los moderadores. Busca u ordena para explorar todas las promesas.",
|
||||
"sectionActive": "Promesas activas",
|
||||
"sectionUpcoming": "Promesas próximas",
|
||||
"sectionPast": "Promesas pasadas",
|
||||
@@ -355,8 +355,8 @@
|
||||
"myGroupsTagline": "Grupos que fundaste, moderas o sigues.",
|
||||
"featuredGroups": "Grupos destacados",
|
||||
"featuredGroupsTagline": "Grupos destacados que merecen tu atención.",
|
||||
"allGroups": "Todos los grupos",
|
||||
"allGroupsTagline": "Explora grupos o busca.",
|
||||
"allGroups": "Grupos",
|
||||
"allGroupsTagline": "Destacados por los moderadores. Busca u ordena para explorar todos los grupos.",
|
||||
"loginToSeeTitle": "Inicia sesión para ver tus grupos",
|
||||
"loginToSeeBody": "Los grupos que fundaste o moderas aparecerán aquí.",
|
||||
"noGroupsTitle": "Aún no hay grupos",
|
||||
@@ -720,10 +720,10 @@
|
||||
"noMatchHint": "Prueba con otro término de búsqueda o bórrala."
|
||||
},
|
||||
"all": {
|
||||
"title": "Todas las campañas",
|
||||
"title": "Campañas",
|
||||
"seoTitle": "Todas las campañas",
|
||||
"description": "Explora todas las campañas publicadas en Agora.",
|
||||
"sectionTagline": "Explora cada causa en la red.",
|
||||
"sectionTagline": "Destacadas por los moderadores. Busca u ordena para explorar toda la red.",
|
||||
"heroKicker": "Campañas",
|
||||
"heroHeading": "Cada causa,",
|
||||
"heroHeadingLine2": "en un solo lugar.",
|
||||
|
||||
+6
-6
@@ -220,8 +220,8 @@
|
||||
"myPledgesTagline": "تعهدهایی که ایجاد کردهای.",
|
||||
"featuredPledges": "تعهدهای ویژه",
|
||||
"featuredPledgesTagline": "تعهدهایی که تیم {{appName}} برجسته کرده است.",
|
||||
"allPledges": "همهٔ تعهدها",
|
||||
"allPledgesTagline": "همهٔ تعهدهای موجود در شبکه را مرور کن.",
|
||||
"allPledges": "تعهدها",
|
||||
"allPledgesTagline": "برگزیدهٔ ناظران. برای مرور همهٔ تعهدها جستجو یا مرتبسازی کن.",
|
||||
"sectionActive": "تعهدهای فعال",
|
||||
"sectionUpcoming": "تعهدهای پیشرو",
|
||||
"sectionPast": "تعهدهای گذشته",
|
||||
@@ -355,8 +355,8 @@
|
||||
"myGroupsTagline": "گروههایی که ساختهای، مدیریت میکنی یا دنبال میکنی.",
|
||||
"featuredGroups": "گروههای ویژه",
|
||||
"featuredGroupsTagline": "گروههای برجستهای که ارزش توجه تو را دارند.",
|
||||
"allGroups": "همهٔ گروهها",
|
||||
"allGroupsTagline": "گروهها را مرور کن یا جستجو کن.",
|
||||
"allGroups": "گروهها",
|
||||
"allGroupsTagline": "برگزیدهٔ ناظران. برای مرور همهٔ گروهها جستجو یا مرتبسازی کن.",
|
||||
"loginToSeeTitle": "برای دیدن گروههایت وارد شو",
|
||||
"loginToSeeBody": "گروههایی که ساختهای یا مدیریت میکنی اینجا ظاهر میشوند.",
|
||||
"noGroupsTitle": "هنوز گروهی نیست",
|
||||
@@ -720,10 +720,10 @@
|
||||
"noMatchHint": "عبارت جستجوی دیگری را امتحان کنید، یا جستجو را پاک کنید."
|
||||
},
|
||||
"all": {
|
||||
"title": "همه کمپینها",
|
||||
"title": "کمپینها",
|
||||
"seoTitle": "همه کمپینها",
|
||||
"description": "همه کمپینهای منتشرشده در Agora را مرور کنید.",
|
||||
"sectionTagline": "هر هدفی را در شبکه مرور کنید.",
|
||||
"sectionTagline": "برگزیدهٔ ناظران. برای مرور کل شبکه جستجو یا مرتبسازی کن.",
|
||||
"heroKicker": "کمپینها",
|
||||
"heroHeading": "هر هدف،",
|
||||
"heroHeadingLine2": "در یک جا.",
|
||||
|
||||
+6
-6
@@ -642,8 +642,8 @@
|
||||
"myPledgesTagline": "Les promesses que vous avez créées.",
|
||||
"featuredPledges": "Promesses en vedette",
|
||||
"featuredPledgesTagline": "Promesses mises en avant par l'équipe {{appName}}.",
|
||||
"allPledges": "Toutes les promesses",
|
||||
"allPledgesTagline": "Parcourez toutes les promesses du réseau.",
|
||||
"allPledges": "Promesses",
|
||||
"allPledgesTagline": "Sélectionnées par les modérateurs. Recherchez ou triez pour parcourir toutes les promesses.",
|
||||
"sectionActive": "Promesses actives",
|
||||
"sectionUpcoming": "Promesses à venir",
|
||||
"sectionPast": "Promesses passées",
|
||||
@@ -777,8 +777,8 @@
|
||||
"myGroupsTagline": "Les groupes que vous avez fondés, modérez ou suivez.",
|
||||
"featuredGroups": "Groupes mis en avant",
|
||||
"featuredGroupsTagline": "Des groupes remarquables qui méritent votre attention.",
|
||||
"allGroups": "Tous les groupes",
|
||||
"allGroupsTagline": "Parcourez les groupes ou recherchez.",
|
||||
"allGroups": "Groupes",
|
||||
"allGroupsTagline": "Sélectionnés par les modérateurs. Recherchez ou triez pour parcourir tous les groupes.",
|
||||
"loginToSeeTitle": "Connectez-vous pour voir vos groupes",
|
||||
"loginToSeeBody": "Les groupes que vous avez fondés ou modérés apparaîtront ici.",
|
||||
"noGroupsTitle": "Aucun groupe pour l'instant",
|
||||
@@ -1142,10 +1142,10 @@
|
||||
"noMatchHint": "Essayez un autre terme de recherche, ou effacez la recherche."
|
||||
},
|
||||
"all": {
|
||||
"title": "Toutes les campagnes",
|
||||
"title": "Campagnes",
|
||||
"seoTitle": "Toutes les campagnes",
|
||||
"description": "Parcourez toutes les campagnes publiées sur Agora.",
|
||||
"sectionTagline": "Parcourez toutes les causes du réseau.",
|
||||
"sectionTagline": "Sélectionnées par les modérateurs. Recherchez ou triez pour parcourir tout le réseau.",
|
||||
"heroKicker": "Campagnes",
|
||||
"heroHeading": "Chaque cause,",
|
||||
"heroHeadingLine2": "au même endroit.",
|
||||
|
||||
+6
-6
@@ -652,8 +652,8 @@
|
||||
"myPledgesTagline": "आपके बनाए हुए प्लेज।",
|
||||
"featuredPledges": "विशेष प्लेज",
|
||||
"featuredPledgesTagline": "{{appName}} टीम द्वारा चुने गए प्लेज।",
|
||||
"allPledges": "सभी प्लेज",
|
||||
"allPledgesTagline": "नेटवर्क पर मौजूद हर प्लेज देखें।",
|
||||
"allPledges": "प्लेज",
|
||||
"allPledgesTagline": "मॉडरेटर द्वारा चयनित। हर प्लेज देखने के लिए खोजें या क्रमबद्ध करें।",
|
||||
"sectionActive": "सक्रिय प्लेज",
|
||||
"sectionUpcoming": "आने वाले प्लेज",
|
||||
"sectionPast": "पिछले प्लेज",
|
||||
@@ -787,8 +787,8 @@
|
||||
"myGroupsTagline": "जिन ग्रुप को आपने बनाया, मॉडरेट किया, या फ़ॉलो किया है।",
|
||||
"featuredGroups": "फ़ीचर्ड ग्रुप",
|
||||
"featuredGroupsTagline": "आपके ध्यान के लायक ख़ास ग्रुप।",
|
||||
"allGroups": "सभी ग्रुप",
|
||||
"allGroupsTagline": "ग्रुप ब्राउज़ करें या खोजें।",
|
||||
"allGroups": "ग्रुप",
|
||||
"allGroupsTagline": "मॉडरेटर द्वारा चयनित। हर ग्रुप देखने के लिए खोजें या क्रमबद्ध करें।",
|
||||
"loginToSeeTitle": "अपने ग्रुप देखने के लिए लॉग इन करें",
|
||||
"loginToSeeBody": "आपने जो ग्रुप बनाए या मॉडरेट किए हैं वे यहाँ दिखेंगे।",
|
||||
"noGroupsTitle": "अभी कोई ग्रुप नहीं",
|
||||
@@ -1152,10 +1152,10 @@
|
||||
"noMatchHint": "अलग खोज शब्द आज़माएँ, या खोज साफ़ करें।"
|
||||
},
|
||||
"all": {
|
||||
"title": "सभी कैंपेन",
|
||||
"title": "कैंपेन",
|
||||
"seoTitle": "सभी कैंपेन",
|
||||
"description": "Agora पर पब्लिश हुए हर कैंपेन को देखें।",
|
||||
"sectionTagline": "नेटवर्क पर हर मक़सद को देखें।",
|
||||
"sectionTagline": "मॉडरेटर द्वारा चयनित। पूरे नेटवर्क को देखने के लिए खोजें या क्रमबद्ध करें।",
|
||||
"heroKicker": "कैंपेन",
|
||||
"heroHeading": "हर मक़सद,",
|
||||
"heroHeadingLine2": "एक ही जगह।",
|
||||
|
||||
+6
-6
@@ -652,8 +652,8 @@
|
||||
"myPledgesTagline": "Ikrar yang Anda buat.",
|
||||
"featuredPledges": "Ikrar pilihan",
|
||||
"featuredPledgesTagline": "Ikrar yang disorot oleh tim {{appName}}.",
|
||||
"allPledges": "Semua ikrar",
|
||||
"allPledgesTagline": "Jelajahi setiap ikrar di jaringan.",
|
||||
"allPledges": "Ikrar",
|
||||
"allPledgesTagline": "Disorot oleh moderator. Cari atau urutkan untuk menjelajahi setiap ikrar.",
|
||||
"sectionActive": "Ikrar aktif",
|
||||
"sectionUpcoming": "Ikrar mendatang",
|
||||
"sectionPast": "Ikrar lampau",
|
||||
@@ -787,8 +787,8 @@
|
||||
"myGroupsTagline": "Grup yang Anda dirikan, moderasi, atau ikuti.",
|
||||
"featuredGroups": "Grup unggulan",
|
||||
"featuredGroupsTagline": "Grup menonjol yang layak Anda perhatikan.",
|
||||
"allGroups": "Semua grup",
|
||||
"allGroupsTagline": "Jelajahi grup atau cari.",
|
||||
"allGroups": "Grup",
|
||||
"allGroupsTagline": "Disorot oleh moderator. Cari atau urutkan untuk menjelajahi setiap grup.",
|
||||
"loginToSeeTitle": "Masuk untuk melihat grup Anda",
|
||||
"loginToSeeBody": "Grup yang Anda dirikan atau moderasi akan muncul di sini.",
|
||||
"noGroupsTitle": "Belum ada grup",
|
||||
@@ -1152,10 +1152,10 @@
|
||||
"noMatchHint": "Coba kata pencarian lain, atau bersihkan pencarian."
|
||||
},
|
||||
"all": {
|
||||
"title": "Semua Kampanye",
|
||||
"title": "Kampanye",
|
||||
"seoTitle": "Semua kampanye",
|
||||
"description": "Telusuri setiap kampanye yang dipublikasikan di Agora.",
|
||||
"sectionTagline": "Jelajahi setiap aksi di jaringan.",
|
||||
"sectionTagline": "Disorot oleh moderator. Cari atau urutkan untuk menjelajahi seluruh jaringan.",
|
||||
"heroKicker": "Kampanye",
|
||||
"heroHeading": "Setiap aksi,",
|
||||
"heroHeadingLine2": "dalam satu tempat.",
|
||||
|
||||
+6
-6
@@ -220,8 +220,8 @@
|
||||
"myPledgesTagline": "ការសន្យាដែលអ្នកបានបង្កើត។",
|
||||
"featuredPledges": "ការសន្យាលេចធ្លោ",
|
||||
"featuredPledgesTagline": "ការសន្យាដែលត្រូវបានរំលេចដោយក្រុម {{appName}}។",
|
||||
"allPledges": "ការសន្យាទាំងអស់",
|
||||
"allPledgesTagline": "រកមើលការសន្យាគ្រប់ៗមួយនៅលើបណ្ដាញ។",
|
||||
"allPledges": "ការសន្យា",
|
||||
"allPledgesTagline": "រំលេចដោយអ្នកសម្របសម្រួល។ ស្វែងរក ឬតម្រៀបដើម្បីរកមើលការសន្យាគ្រប់ៗមួយ។",
|
||||
"sectionActive": "ការសន្យាសកម្ម",
|
||||
"sectionUpcoming": "ការសន្យាខាងមុខ",
|
||||
"sectionPast": "ការសន្យាកន្លងមក",
|
||||
@@ -355,8 +355,8 @@
|
||||
"myGroupsTagline": "ក្រុមដែលអ្នកបានបង្កើត គ្រប់គ្រង ឬដាក់តាមដាន។",
|
||||
"featuredGroups": "ក្រុមលេចធ្លោ",
|
||||
"featuredGroupsTagline": "ក្រុមលេចធ្លោដែលសក្តិសមនឹងការយកចិត្តទុកដាក់របស់អ្នក។",
|
||||
"allGroups": "ក្រុមទាំងអស់",
|
||||
"allGroupsTagline": "រកមើលក្រុម ឬស្វែងរក។",
|
||||
"allGroups": "ក្រុម",
|
||||
"allGroupsTagline": "រំលេចដោយអ្នកសម្របសម្រួល។ ស្វែងរក ឬតម្រៀបដើម្បីរកមើលក្រុមគ្រប់ៗមួយ។",
|
||||
"loginToSeeTitle": "ចូលដើម្បីមើលក្រុមរបស់អ្នក",
|
||||
"loginToSeeBody": "ក្រុមដែលអ្នកបានបង្កើត ឬគ្រប់គ្រងនឹងបង្ហាញនៅទីនេះ។",
|
||||
"noGroupsTitle": "មិនទាន់មានក្រុមទេ",
|
||||
@@ -720,10 +720,10 @@
|
||||
"noMatchHint": "សាកល្បងពាក្យស្វែងរកផ្សេង ឬសម្អាតការស្វែងរក។"
|
||||
},
|
||||
"all": {
|
||||
"title": "យុទ្ធនាការទាំងអស់",
|
||||
"title": "យុទ្ធនាការ",
|
||||
"seoTitle": "យុទ្ធនាការទាំងអស់",
|
||||
"description": "រកមើលរាល់យុទ្ធនាការដែលផ្សព្វផ្សាយលើ Agora។",
|
||||
"sectionTagline": "រកមើលរាល់បុព្វហេតុនៅលើបណ្ដាញ។",
|
||||
"sectionTagline": "រំលេចដោយអ្នកសម្របសម្រួល។ ស្វែងរក ឬតម្រៀបដើម្បីរកមើលបណ្ដាញទាំងមូល។",
|
||||
"heroKicker": "យុទ្ធនាការ",
|
||||
"heroHeading": "រាល់បុព្វហេតុ",
|
||||
"heroHeadingLine2": "នៅកន្លែងតែមួយ។",
|
||||
|
||||
+6
-6
@@ -220,8 +220,8 @@
|
||||
"myPledgesTagline": "هغه ژمنې چې تاسو جوړې کړې دي.",
|
||||
"featuredPledges": "ځانګړې ژمنې",
|
||||
"featuredPledgesTagline": "هغه ژمنې چې د {{appName}} ټیم په ګوته کړې دي.",
|
||||
"allPledges": "ټولې ژمنې",
|
||||
"allPledgesTagline": "په شبکه کې هره ژمنه وګورئ.",
|
||||
"allPledges": "ژمنې",
|
||||
"allPledgesTagline": "د څارونکو لخوا ځانګړې شوې. د ټولو ژمنو لیدلو لپاره لټون یا ترتیب وکړئ.",
|
||||
"sectionActive": "فعالې ژمنې",
|
||||
"sectionUpcoming": "راتلونکې ژمنې",
|
||||
"sectionPast": "تېرې ژمنې",
|
||||
@@ -355,8 +355,8 @@
|
||||
"myGroupsTagline": "هغه ډلې چې تاسو یې جوړې کړي، اداره کوئ، یا یې تعقیبوئ.",
|
||||
"featuredGroups": "ځانګړې ډلې",
|
||||
"featuredGroupsTagline": "ځانګړې ډلې چې ستاسو د پاملرنې وړ دي.",
|
||||
"allGroups": "ټولې ډلې",
|
||||
"allGroupsTagline": "ډلې وڅیړئ یا لټون وکړئ.",
|
||||
"allGroups": "ډلې",
|
||||
"allGroupsTagline": "د څارونکو لخوا ځانګړې شوې. د ټولو ډلو لیدلو لپاره لټون یا ترتیب وکړئ.",
|
||||
"loginToSeeTitle": "د خپلو ډلو لیدلو لپاره ننوځئ",
|
||||
"loginToSeeBody": "هغه ډلې چې تاسو یې جوړې کړي یا یې اداره کوئ به دلته راڅرګندې شي.",
|
||||
"noGroupsTitle": "تر اوسه ډلې نشته",
|
||||
@@ -726,10 +726,10 @@
|
||||
"heroBody": "په Nostr کې خپور شوی هر د مرستو راټولولو کمپاین په یوه ځای کې راټول شوی. د بشپړې شبکې لټون وکړئ، هغه هدف ومومئ چې درته اهمیت لري، او په مستقیم ډول یې د بټکوین له لارې ملاتړ وکړئ.",
|
||||
"campaignsCount_one": "په شبکه کې کمپاین",
|
||||
"campaignsCount_other": "په شبکه کې کمپاینونه",
|
||||
"title": "ټول کمپاینونه",
|
||||
"title": "کمپاینونه",
|
||||
"seoTitle": "ټول کمپاینونه",
|
||||
"description": "په Agora کې خپور شوي ټول کمپاینونه وګورئ.",
|
||||
"sectionTagline": "په شبکه کې هر هدف وپلټئ.",
|
||||
"sectionTagline": "د څارونکو لخوا ځانګړي شوي. د ټولې شبکې لیدلو لپاره لټون یا ترتیب وکړئ.",
|
||||
"searchAriaLabel": "د کمپاینونو لټون",
|
||||
"searchPlaceholder": "د کمپاینونو لټون…",
|
||||
"clearSearch": "د لټون پاکول",
|
||||
|
||||
+6
-6
@@ -652,8 +652,8 @@
|
||||
"myPledgesTagline": "Promessas que você criou.",
|
||||
"featuredPledges": "Promessas em destaque",
|
||||
"featuredPledgesTagline": "Promessas destacadas pela equipe do {{appName}}.",
|
||||
"allPledges": "Todas as promessas",
|
||||
"allPledgesTagline": "Explore todas as promessas da rede.",
|
||||
"allPledges": "Promessas",
|
||||
"allPledgesTagline": "Destacadas pelos moderadores. Pesquise ou ordene para explorar todas as promessas.",
|
||||
"sectionActive": "Promessas ativas",
|
||||
"sectionUpcoming": "Promessas futuras",
|
||||
"sectionPast": "Promessas passadas",
|
||||
@@ -787,8 +787,8 @@
|
||||
"myGroupsTagline": "Grupos que você fundou, modera ou segue.",
|
||||
"featuredGroups": "Grupos em destaque",
|
||||
"featuredGroupsTagline": "Grupos que se destacam e merecem sua atenção.",
|
||||
"allGroups": "Todos os grupos",
|
||||
"allGroupsTagline": "Explore os grupos ou pesquise.",
|
||||
"allGroups": "Grupos",
|
||||
"allGroupsTagline": "Destacados pelos moderadores. Pesquise ou ordene para explorar todos os grupos.",
|
||||
"loginToSeeTitle": "Entre para ver seus grupos",
|
||||
"loginToSeeBody": "Grupos que você fundou ou modera aparecerão aqui.",
|
||||
"noGroupsTitle": "Nenhum grupo ainda",
|
||||
@@ -1152,10 +1152,10 @@
|
||||
"noMatchHint": "Tente um termo de pesquisa diferente, ou limpe a pesquisa."
|
||||
},
|
||||
"all": {
|
||||
"title": "Todas as campanhas",
|
||||
"title": "Campanhas",
|
||||
"seoTitle": "Todas as campanhas",
|
||||
"description": "Navegue por todas as campanhas publicadas no Agora.",
|
||||
"sectionTagline": "Conheça todas as causas da rede.",
|
||||
"sectionTagline": "Destacadas pelos moderadores. Pesquise ou ordene para explorar toda a rede.",
|
||||
"heroKicker": "Campanhas",
|
||||
"heroHeading": "Cada causa,",
|
||||
"heroHeadingLine2": "em um só lugar.",
|
||||
|
||||
+6
-6
@@ -652,8 +652,8 @@
|
||||
"myPledgesTagline": "Обещания, которые вы создали.",
|
||||
"featuredPledges": "Избранные обещания",
|
||||
"featuredPledgesTagline": "Обещания, отмеченные командой {{appName}}.",
|
||||
"allPledges": "Все обещания",
|
||||
"allPledgesTagline": "Просматривайте все обещания в сети.",
|
||||
"allPledges": "Обещания",
|
||||
"allPledgesTagline": "Отмечено модераторами. Используйте поиск или сортировку, чтобы просмотреть все обещания.",
|
||||
"sectionActive": "Активные обещания",
|
||||
"sectionUpcoming": "Предстоящие обещания",
|
||||
"sectionPast": "Прошлые обещания",
|
||||
@@ -787,8 +787,8 @@
|
||||
"myGroupsTagline": "Группы, которые вы основали, модерируете или на которые подписаны.",
|
||||
"featuredGroups": "Избранные группы",
|
||||
"featuredGroupsTagline": "Заметные группы, достойные вашего внимания.",
|
||||
"allGroups": "Все группы",
|
||||
"allGroupsTagline": "Просматривайте группы или ищите.",
|
||||
"allGroups": "Группы",
|
||||
"allGroupsTagline": "Отмечено модераторами. Используйте поиск или сортировку, чтобы просмотреть все группы.",
|
||||
"loginToSeeTitle": "Войдите, чтобы увидеть свои группы",
|
||||
"loginToSeeBody": "Группы, которые вы основали или модерируете, появятся здесь.",
|
||||
"noGroupsTitle": "Пока нет групп",
|
||||
@@ -1152,10 +1152,10 @@
|
||||
"noMatchHint": "Попробуйте другой поисковый запрос или очистите поиск."
|
||||
},
|
||||
"all": {
|
||||
"title": "Все кампании",
|
||||
"title": "Кампании",
|
||||
"seoTitle": "Все кампании",
|
||||
"description": "Просмотрите все кампании, опубликованные на Agora.",
|
||||
"sectionTagline": "Просмотрите каждое дело в сети.",
|
||||
"sectionTagline": "Отмечено модераторами. Используйте поиск или сортировку, чтобы просмотреть всю сеть.",
|
||||
"heroKicker": "Кампании",
|
||||
"heroHeading": "Каждое дело —",
|
||||
"heroHeadingLine2": "в одном месте.",
|
||||
|
||||
+6
-6
@@ -220,8 +220,8 @@
|
||||
"myPledgesTagline": "Zvitsidziro zvawakagadzira.",
|
||||
"featuredPledges": "Zvitsidziro zvakasarudzwa",
|
||||
"featuredPledgesTagline": "Zvitsidziro zvakasimudzirwa nechikwata che{{appName}}.",
|
||||
"allPledges": "Zvitsidziro zvose",
|
||||
"allPledgesTagline": "Tarisa chitsidziro chega chega chiri pamutambo wenetiweki.",
|
||||
"allPledges": "Zvitsidziro",
|
||||
"allPledgesTagline": "Zvakatarisirwa nevatariri. Tsvaga kana kuronga kuti utarise chitsidziro chega chega.",
|
||||
"sectionActive": "Zvitsidziro zviri kushanda",
|
||||
"sectionUpcoming": "Zvitsidziro zviri kuuya",
|
||||
"sectionPast": "Zvitsidziro zvapfuura",
|
||||
@@ -355,8 +355,8 @@
|
||||
"myGroupsTagline": "Mapoka awakatanga, aunotungamira, kana aunotevera.",
|
||||
"featuredGroups": "Mapoka anokurumbira",
|
||||
"featuredGroupsTagline": "Mapoka anobudirira anokodzera kutariswa nemi.",
|
||||
"allGroups": "Mapoka ose",
|
||||
"allGroupsTagline": "Tarisa mapoka kana kutsvaga.",
|
||||
"allGroups": "Mapoka",
|
||||
"allGroupsTagline": "Zvakatarisirwa nevatariri. Tsvaga kana kuronga kuti utarise boka rega rega.",
|
||||
"loginToSeeTitle": "Pinda kuti uone mapoka ako",
|
||||
"loginToSeeBody": "Mapoka awakatanga kana aunotungamira achaonekwa pano.",
|
||||
"noGroupsTitle": "Hapana mapoka parizvino",
|
||||
@@ -720,10 +720,10 @@
|
||||
"noMatchHint": "Edza chimwe chinotsvagwa, kana ubvise kutsvaga."
|
||||
},
|
||||
"all": {
|
||||
"title": "Mishandirapamwe Yose",
|
||||
"title": "Mishandirapamwe",
|
||||
"seoTitle": "Mishandirapamwe yose",
|
||||
"description": "Tarisa mishandirapamwe yose yakaiswa paAgora.",
|
||||
"sectionTagline": "Tarisa chinangwa chimwe nechimwe panetwork.",
|
||||
"sectionTagline": "Zvakatarisirwa nevatariri. Tsvaga kana kuronga kuti utarise netiweki yose.",
|
||||
"heroKicker": "Mishandirapamwe",
|
||||
"heroHeading": "Chinangwa chimwe nechimwe,",
|
||||
"heroHeadingLine2": "panzvimbo imwe chete.",
|
||||
|
||||
+6
-6
@@ -651,8 +651,8 @@
|
||||
"myPledgesTagline": "Ahadi ulizounda.",
|
||||
"featuredPledges": "Ahadi maalum",
|
||||
"featuredPledgesTagline": "Ahadi zilizoangaziwa na timu ya {{appName}}.",
|
||||
"allPledges": "Ahadi zote",
|
||||
"allPledgesTagline": "Vinjari kila ahadi kwenye mtandao.",
|
||||
"allPledges": "Ahadi",
|
||||
"allPledgesTagline": "Zimeangaziwa na wasimamizi. Tafuta au panga ili kuvinjari kila ahadi.",
|
||||
"sectionActive": "Ahadi zinazoendelea",
|
||||
"sectionUpcoming": "Ahadi zijazo",
|
||||
"sectionPast": "Ahadi zilizopita",
|
||||
@@ -786,8 +786,8 @@
|
||||
"myGroupsTagline": "Vikundi ulivyounda, unavyosimamia, au unavyofuata.",
|
||||
"featuredGroups": "Vikundi maarufu",
|
||||
"featuredGroupsTagline": "Vikundi vinavyojitokeza vinavyostahili usikivu wako.",
|
||||
"allGroups": "Vikundi vyote",
|
||||
"allGroupsTagline": "Vinjari vikundi au tafuta.",
|
||||
"allGroups": "Vikundi",
|
||||
"allGroupsTagline": "Vimeangaziwa na wasimamizi. Tafuta au panga ili kuvinjari kila kikundi.",
|
||||
"loginToSeeTitle": "Ingia ili kuona vikundi vyako",
|
||||
"loginToSeeBody": "Vikundi ulivyounda au unavyosimamia vitaonekana hapa.",
|
||||
"noGroupsTitle": "Hakuna vikundi bado",
|
||||
@@ -1151,10 +1151,10 @@
|
||||
"noMatchHint": "Jaribu neno tofauti la utafutaji, au futa utafutaji."
|
||||
},
|
||||
"all": {
|
||||
"title": "Kampeni Zote",
|
||||
"title": "Kampeni",
|
||||
"seoTitle": "Kampeni zote",
|
||||
"description": "Vinjari kila kampeni iliyochapishwa kwenye Agora.",
|
||||
"sectionTagline": "Vinjari kila lengo kwenye mtandao.",
|
||||
"sectionTagline": "Zimeangaziwa na wasimamizi. Tafuta au panga ili kuvinjari mtandao mzima.",
|
||||
"searchAriaLabel": "Tafuta kampeni",
|
||||
"searchPlaceholder": "Tafuta kampeni…",
|
||||
"clearSearch": "Futa utafutaji",
|
||||
|
||||
+6
-6
@@ -651,8 +651,8 @@
|
||||
"myPledgesTagline": "Oluşturduğunuz taahhütler.",
|
||||
"featuredPledges": "Öne çıkan taahhütler",
|
||||
"featuredPledgesTagline": "{{appName}} ekibinin öne çıkardığı taahhütler.",
|
||||
"allPledges": "Tüm taahhütler",
|
||||
"allPledgesTagline": "Ağdaki her taahhüde göz atın.",
|
||||
"allPledges": "Taahhütler",
|
||||
"allPledgesTagline": "Moderatörler tarafından öne çıkarıldı. Her taahhüde göz atmak için arama yapın veya sıralayın.",
|
||||
"sectionActive": "Aktif taahhütler",
|
||||
"sectionUpcoming": "Yaklaşan taahhütler",
|
||||
"sectionPast": "Geçmiş taahhütler",
|
||||
@@ -786,8 +786,8 @@
|
||||
"myGroupsTagline": "Kurduğunuz, yönettiğiniz veya takip ettiğiniz gruplar.",
|
||||
"featuredGroups": "Öne çıkan gruplar",
|
||||
"featuredGroupsTagline": "Dikkatinize değer öne çıkan gruplar.",
|
||||
"allGroups": "Tüm gruplar",
|
||||
"allGroupsTagline": "Gruplara göz atın veya arama yapın.",
|
||||
"allGroups": "Gruplar",
|
||||
"allGroupsTagline": "Moderatörler tarafından öne çıkarıldı. Her gruba göz atmak için arama yapın veya sıralayın.",
|
||||
"loginToSeeTitle": "Gruplarınızı görmek için giriş yapın",
|
||||
"loginToSeeBody": "Kurduğunuz veya yönettiğiniz gruplar burada görünecek.",
|
||||
"noGroupsTitle": "Henüz grup yok",
|
||||
@@ -1151,10 +1151,10 @@
|
||||
"noMatchHint": "Farklı bir arama terimi deneyin ya da aramayı temizleyin."
|
||||
},
|
||||
"all": {
|
||||
"title": "Tüm Kampanyalar",
|
||||
"title": "Kampanyalar",
|
||||
"seoTitle": "Tüm kampanyalar",
|
||||
"description": "Agora'da yayımlanmış her kampanyaya göz atın.",
|
||||
"sectionTagline": "Ağdaki her davaya göz atın.",
|
||||
"sectionTagline": "Moderatörler tarafından öne çıkarıldı. Ağın tamamına göz atmak için arama yapın veya sıralayın.",
|
||||
"searchAriaLabel": "Kampanyaları ara",
|
||||
"searchPlaceholder": "Kampanya ara…",
|
||||
"clearSearch": "Aramayı temizle",
|
||||
|
||||
@@ -220,8 +220,8 @@
|
||||
"myPledgesTagline": "你建立的懸賞。",
|
||||
"featuredPledges": "精選懸賞",
|
||||
"featuredPledgesTagline": "{{appName}} 團隊重點推薦的懸賞。",
|
||||
"allPledges": "全部懸賞",
|
||||
"allPledgesTagline": "瀏覽網路上的每一個懸賞。",
|
||||
"allPledges": "懸賞",
|
||||
"allPledgesTagline": "由版主重點推薦。搜尋或排序以瀏覽所有懸賞。",
|
||||
"sectionActive": "進行中的懸賞",
|
||||
"sectionUpcoming": "即將開始的懸賞",
|
||||
"sectionPast": "已結束的懸賞",
|
||||
@@ -355,8 +355,8 @@
|
||||
"myGroupsTagline": "你建立、管理或追蹤的群組。",
|
||||
"featuredGroups": "精選群組",
|
||||
"featuredGroupsTagline": "值得你關注的出色群組。",
|
||||
"allGroups": "全部群組",
|
||||
"allGroupsTagline": "瀏覽群組或搜尋。",
|
||||
"allGroups": "群組",
|
||||
"allGroupsTagline": "由版主重點推薦。搜尋或排序以瀏覽所有群組。",
|
||||
"loginToSeeTitle": "登入以檢視你的群組",
|
||||
"loginToSeeBody": "你建立或管理的群組會顯示在這裡。",
|
||||
"noGroupsTitle": "還沒有群組",
|
||||
@@ -720,10 +720,10 @@
|
||||
"noMatchHint": "請嘗試不同的搜尋字詞,或清除搜尋。"
|
||||
},
|
||||
"all": {
|
||||
"title": "所有活動",
|
||||
"title": "活動",
|
||||
"seoTitle": "所有活動",
|
||||
"description": "瀏覽 Agora 上釋出的所有活動。",
|
||||
"sectionTagline": "瀏覽網路上的每一項事業。",
|
||||
"sectionTagline": "由版主重點推薦。搜尋或排序以瀏覽整個網路。",
|
||||
"heroKicker": "活動",
|
||||
"heroHeading": "每一份心意,",
|
||||
"heroHeadingLine2": "匯聚於此。",
|
||||
|
||||
+6
-6
@@ -220,8 +220,8 @@
|
||||
"myPledgesTagline": "你创建的悬赏。",
|
||||
"featuredPledges": "精选悬赏",
|
||||
"featuredPledgesTagline": "{{appName}} 团队重点推荐的悬赏。",
|
||||
"allPledges": "全部悬赏",
|
||||
"allPledgesTagline": "浏览网络上的每一个悬赏。",
|
||||
"allPledges": "悬赏",
|
||||
"allPledgesTagline": "由版主精选推荐。可搜索或排序以浏览全部悬赏。",
|
||||
"sectionActive": "进行中的悬赏",
|
||||
"sectionUpcoming": "即将开始的悬赏",
|
||||
"sectionPast": "已结束的悬赏",
|
||||
@@ -355,8 +355,8 @@
|
||||
"myGroupsTagline": "你创建、管理或关注的群组。",
|
||||
"featuredGroups": "精选群组",
|
||||
"featuredGroupsTagline": "值得你关注的出色群组。",
|
||||
"allGroups": "全部群组",
|
||||
"allGroupsTagline": "浏览群组或搜索。",
|
||||
"allGroups": "群组",
|
||||
"allGroupsTagline": "由版主精选推荐。可搜索或排序以浏览全部群组。",
|
||||
"loginToSeeTitle": "登录以查看你的群组",
|
||||
"loginToSeeBody": "你创建或管理的群组会显示在这里。",
|
||||
"noGroupsTitle": "还没有群组",
|
||||
@@ -720,10 +720,10 @@
|
||||
"noMatchHint": "尝试其他搜索词,或清除搜索。"
|
||||
},
|
||||
"all": {
|
||||
"title": "所有活动",
|
||||
"title": "活动",
|
||||
"seoTitle": "所有活动",
|
||||
"description": "浏览 Agora 上发布的所有活动。",
|
||||
"sectionTagline": "浏览网络上的每一项事业。",
|
||||
"sectionTagline": "由版主精选推荐。可搜索或排序以浏览整个网络。",
|
||||
"heroKicker": "活动",
|
||||
"heroHeading": "每一个理念,",
|
||||
"heroHeadingLine2": "汇聚于此。",
|
||||
|
||||
+47
-43
@@ -36,7 +36,7 @@ import {
|
||||
import {
|
||||
HandHeart, PlusCircle, ChevronDown, ChevronUp, Loader2,
|
||||
Link as LinkIcon, Check, MoreHorizontal, Trash2,
|
||||
Megaphone, Sparkles, EyeOff,
|
||||
Megaphone, EyeOff,
|
||||
} from 'lucide-react';
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
@@ -323,6 +323,10 @@ export default function ActionsPage() {
|
||||
return { searchHits: visible, searchHiddenCount: hidden };
|
||||
}, [searchHitsRaw, pledgeModeration, canShowHidden]);
|
||||
|
||||
// `actions` is the chronological pledge list filtered by country, with
|
||||
// moderator-hidden items dropped (unless `showHidden` is on). Featured
|
||||
// pledges are NOT excluded here — the idle render path pulls them
|
||||
// separately, and the active render path shows the full list.
|
||||
const { actions, listHiddenCount } = useMemo(() => {
|
||||
if (!rawActions) return { actions: undefined, listHiddenCount: 0 };
|
||||
const hiddenCoords = pledgeModeration?.hiddenCoords ?? new Set<string>();
|
||||
@@ -365,16 +369,18 @@ export default function ActionsPage() {
|
||||
|
||||
const DEFAULT_VISIBLE = 4;
|
||||
const [showAllMine, setShowAllMine] = useState(false);
|
||||
const [showAllFeatured, setShowAllFeatured] = useState(false);
|
||||
const [showAllPledges, setShowAllPledges] = useState(false);
|
||||
|
||||
const allPledges = useMemo(
|
||||
() => (actions ?? []).filter((action) => !featuredPledgeCoordSet.has(getPledgeCoord(action))),
|
||||
[actions, featuredPledgeCoordSet],
|
||||
);
|
||||
const visibleMine = showAllMine ? (myPledges ?? []) : (myPledges ?? []).slice(0, DEFAULT_VISIBLE);
|
||||
const visibleFeatured = showAllFeatured ? orderedFeaturedPledges : orderedFeaturedPledges.slice(0, DEFAULT_VISIBLE);
|
||||
const visibleAllPledges = showAllPledges ? allPledges : allPledges.slice(0, DEFAULT_VISIBLE);
|
||||
|
||||
// Idle list: featured first; if none are featured, fall back to the
|
||||
// chronological all-pledges grid so the page is never blank.
|
||||
const idlePledges = useMemo<Action[]>(() => {
|
||||
if (orderedFeaturedPledges.length > 0) return orderedFeaturedPledges;
|
||||
return (actions ?? []).filter(
|
||||
(action) => !featuredPledgeCoordSet.has(getPledgeCoord(action)),
|
||||
);
|
||||
}, [orderedFeaturedPledges, actions, featuredPledgeCoordSet]);
|
||||
|
||||
const hiddenPledges = useMemo<Action[]>(() => {
|
||||
if (!isMod || !pledgeModerationReady) return [];
|
||||
return (allPledgesForMods ?? []).filter((pledge) => pledgeModeration.hiddenCoords.has(getPledgeCoord(pledge)));
|
||||
@@ -386,6 +392,7 @@ export default function ActionsPage() {
|
||||
onQueryChange={setSearchInput}
|
||||
sort={sortMode}
|
||||
onSortChange={setSortMode}
|
||||
sortOptions={['top', 'new']}
|
||||
searchPlaceholderKey="pledges.list.searchPlaceholder"
|
||||
searchAriaLabelKey="pledges.list.searchAriaLabel"
|
||||
showHidden={isMod ? {
|
||||
@@ -424,37 +431,18 @@ export default function ActionsPage() {
|
||||
</section>
|
||||
)}
|
||||
|
||||
{orderedFeaturedPledges.length > 0 && (
|
||||
<section className="space-y-5">
|
||||
<div>
|
||||
<h2 className="text-2xl sm:text-3xl font-bold tracking-tight inline-flex items-center gap-2">
|
||||
<Sparkles className="size-6 text-primary" />
|
||||
{t('pledges.list.featuredPledges')}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground mt-1">{t('pledges.list.featuredPledgesTagline')}</p>
|
||||
</div>
|
||||
<ActionSection
|
||||
items={visibleFeatured}
|
||||
total={orderedFeaturedPledges.length}
|
||||
visible={DEFAULT_VISIBLE}
|
||||
showAll={showAllFeatured}
|
||||
onToggle={() => setShowAllFeatured(!showAllFeatured)}
|
||||
btcPrice={btcPrice}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* Unified Pledges section.
|
||||
- Idle (no search / no sort / no country): renders the
|
||||
moderator-featured grid, or a chronological fallback when
|
||||
no pledges are featured yet.
|
||||
- Active: renders the full search/sort/country result set. */}
|
||||
<section className="space-y-5">
|
||||
<div className="flex flex-col items-stretch gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl sm:text-3xl font-bold tracking-tight">
|
||||
{trimmedSearch
|
||||
? t('common.search')
|
||||
: isSearching && sortMode === 'top'
|
||||
? t('common.sortTop')
|
||||
: isSearching && sortMode === 'new'
|
||||
? t('common.sortNew')
|
||||
: t('pledges.list.allPledges')}
|
||||
: t('pledges.list.allPledges')}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{isSearching && searchHits
|
||||
@@ -521,15 +509,31 @@ export default function ActionsPage() {
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
{Array.from({ length: 8 }).map((_, i) => <ActionSkeleton key={i} />)}
|
||||
</div>
|
||||
) : allPledges.length > 0 ? (
|
||||
<ActionSection
|
||||
items={visibleAllPledges}
|
||||
total={allPledges.length}
|
||||
visible={DEFAULT_VISIBLE}
|
||||
showAll={showAllPledges}
|
||||
onToggle={() => setShowAllPledges(!showAllPledges)}
|
||||
btcPrice={btcPrice}
|
||||
/>
|
||||
) : idlePledges.length > 0 ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
{idlePledges.map((action) => (
|
||||
<PledgeCard
|
||||
key={`${action.pubkey}:${action.id}`}
|
||||
action={action}
|
||||
btcPrice={btcPrice}
|
||||
showAuthor
|
||||
showTranslate
|
||||
topRight={
|
||||
<>
|
||||
<ModerationOverlay
|
||||
coord={getPledgeCoord(action)}
|
||||
entityTitle={action.title}
|
||||
surface="pledge"
|
||||
axes={['hide', 'featured']}
|
||||
showMenu={false}
|
||||
className="flex items-center"
|
||||
/>
|
||||
<ActionShareMenu action={action} displayTitle={action.title} />
|
||||
</>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<Card className="border-dashed">
|
||||
<div className="py-12 px-8 text-center space-y-4">
|
||||
|
||||
+142
-136
@@ -23,38 +23,47 @@ import { cn } from '@/lib/utils';
|
||||
import type { Nip50Sort } from '@/hooks/useNip50Search';
|
||||
import type { ParsedCampaign } from '@/lib/campaign';
|
||||
|
||||
/** Type-guard for the `?sort=` URL param. Default is `top` (most-zapped). */
|
||||
function parseSort(value: string | null): CampaignSort {
|
||||
return value === 'none' ? 'none' : 'top';
|
||||
/**
|
||||
* Type-guard for the `?sort=` URL param.
|
||||
*
|
||||
* - `top` and `new` map to the toolbar's active sort modes.
|
||||
* - Anything else (missing, empty, legacy values) collapses to
|
||||
* `'default'`, the curated featured-first idle state.
|
||||
*/
|
||||
function parseSort(value: string | null): Nip50Sort {
|
||||
if (value === 'top') return 'top';
|
||||
if (value === 'new') return 'new';
|
||||
return 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Map between the shared toolbar's sort vocabulary (`default` / `top` /
|
||||
* `new`) and the `useAllCampaigns` hook's vocabulary (`top` / `none`).
|
||||
*
|
||||
* AllCampaignsPage doesn't have a curated/default layout — it's the
|
||||
* "show me everything" page — so the toolbar's 'default' option falls
|
||||
* through to 'top' here, the page's canonical ranked view. The legacy
|
||||
* `none` value is preserved on the URL so existing share links keep
|
||||
* working.
|
||||
* Map the toolbar's sort vocabulary (`default` / `top` / `new`) to the
|
||||
* `useAllCampaigns` hook's vocabulary (`top` / `none`). `'new'` and
|
||||
* `'default'` both map to `'none'` (chronological) — the page handles
|
||||
* the "show featured only" framing on top of that.
|
||||
*/
|
||||
const toToolbarSort = (s: CampaignSort): Nip50Sort => (s === 'none' ? 'new' : 'top');
|
||||
const toQuerySort = (s: Nip50Sort): CampaignSort => (s === 'new' ? 'none' : 'top');
|
||||
const toQuerySort = (s: Nip50Sort): CampaignSort => (s === 'top' ? 'top' : 'none');
|
||||
|
||||
/**
|
||||
* Lists every campaign found on relays. Two sort modes:
|
||||
* Lists every campaign found on relays. The page has two display modes:
|
||||
*
|
||||
* - **Top** (default): ranked by total sats raised (kind 8333 donation receipts).
|
||||
* - **New**: chronological by `created_at`.
|
||||
* 1. **Idle** (no search, no sort, no country picked) — shows
|
||||
* moderator-featured campaigns only. If there are no featured
|
||||
* campaigns, falls back to the latest chronological grid so the page
|
||||
* is never blank.
|
||||
* 2. **Active** (user typed a query, picked Top/New, or chose a
|
||||
* country) — shows the full set, ranked by sats raised (Top) or
|
||||
* chronological (New / search default).
|
||||
*
|
||||
* Both modes share a free-text search bar that filters across title,
|
||||
* summary, story, location, and category tags client-side.
|
||||
* Search filters across title, summary, story, location, and category
|
||||
* tags client-side.
|
||||
*
|
||||
* Hidden campaigns are excluded by default — flip the "Show hidden"
|
||||
* toggle (inside the toolbar's filter popover) to include them.
|
||||
*
|
||||
* URL state: `?sort=none&q=<search>`. Default values are stripped so the
|
||||
* canonical URL stays clean. Useful for sharing search results.
|
||||
* URL state: `?sort=top|new&q=<search>&country=<iso>`. Default values
|
||||
* are stripped so the canonical URL stays clean. Useful for sharing
|
||||
* search results.
|
||||
*/
|
||||
export function AllCampaignsPage() {
|
||||
const { t } = useTranslation();
|
||||
@@ -79,7 +88,7 @@ export function AllCampaignsPage() {
|
||||
|
||||
// Sync the debounced search → URL. Empty / default values are stripped
|
||||
// so the canonical URL is `/campaigns/all` (not
|
||||
// `/campaigns/all?sort=none&q=`).
|
||||
// `/campaigns/all?sort=&q=`).
|
||||
useEffect(() => {
|
||||
const next = new URLSearchParams(searchParams);
|
||||
const trimmed = debouncedSearch.trim();
|
||||
@@ -102,8 +111,8 @@ export function AllCampaignsPage() {
|
||||
|
||||
const setSortFromToolbar = (value: Nip50Sort) => {
|
||||
const next = new URLSearchParams(searchParams);
|
||||
const queryValue = toQuerySort(value);
|
||||
if (queryValue === 'none') next.set('sort', 'none');
|
||||
if (value === 'top') next.set('sort', 'top');
|
||||
else if (value === 'new') next.set('sort', 'new');
|
||||
else next.delete('sort');
|
||||
setSearchParams(next, { replace: true });
|
||||
};
|
||||
@@ -118,7 +127,7 @@ export function AllCampaignsPage() {
|
||||
};
|
||||
|
||||
const { data: campaigns, isLoading } = useAllCampaigns({
|
||||
sort,
|
||||
sort: toQuerySort(sort),
|
||||
search: debouncedSearch.trim(),
|
||||
countryCode: urlCountry,
|
||||
limit: 200,
|
||||
@@ -148,10 +157,21 @@ export function AllCampaignsPage() {
|
||||
description: t('campaigns.all.description'),
|
||||
});
|
||||
|
||||
const activeQuery = debouncedSearch.trim();
|
||||
|
||||
// The unified section is in "active" mode when the user has expressed
|
||||
// intent to browse the full set: typed a query, picked Top/New, or
|
||||
// chosen a country. Otherwise it's the curated featured-first view.
|
||||
const isActive = activeQuery !== '' || sort !== 'default' || !!urlCountry;
|
||||
|
||||
// Visible campaigns in the **active** branch: every campaign matching
|
||||
// the search/sort/country, minus hidden (unless the moderator opted
|
||||
// in to seeing hidden). Featured items are intentionally NOT pulled
|
||||
// out of this list — when the user is actively browsing, they want a
|
||||
// ranked or chronological grid, not the curated shelf.
|
||||
const { visible, hiddenCount, hiddenCampaigns } = useMemo(() => {
|
||||
const all = campaigns ?? [];
|
||||
const hiddenCoords = moderation?.hiddenCoords ?? new Set<string>();
|
||||
const featuredCoordSet = new Set(featuredCoords);
|
||||
let hiddenCount = 0;
|
||||
const visible: ParsedCampaign[] = [];
|
||||
const hiddenCampaigns: ParsedCampaign[] = [];
|
||||
@@ -161,13 +181,13 @@ export function AllCampaignsPage() {
|
||||
hiddenCount += 1;
|
||||
hiddenCampaigns.push(c);
|
||||
if (isMod && showHidden) visible.push(c);
|
||||
} else if (!featuredCoordSet.has(c.aTag)) {
|
||||
} else {
|
||||
visible.push(c);
|
||||
}
|
||||
}
|
||||
|
||||
return { visible, hiddenCount, hiddenCampaigns };
|
||||
}, [campaigns, featuredCoords, isMod, moderation, showHidden]);
|
||||
}, [campaigns, isMod, moderation, showHidden]);
|
||||
|
||||
const orderedFeaturedCampaigns = useMemo(() => {
|
||||
if (!featuredCampaigns) return [] as ParsedCampaign[];
|
||||
@@ -176,19 +196,23 @@ export function AllCampaignsPage() {
|
||||
);
|
||||
}, [featuredCampaigns, moderation]);
|
||||
|
||||
// Idle-mode list: featured first; if none are featured, fall back to
|
||||
// the latest chronological grid so the page never lands on an empty
|
||||
// state when there's content to show.
|
||||
const idleCampaigns = useMemo<ParsedCampaign[]>(() => {
|
||||
if (orderedFeaturedCampaigns.length > 0) return orderedFeaturedCampaigns;
|
||||
return visible;
|
||||
}, [orderedFeaturedCampaigns, visible]);
|
||||
|
||||
const DEFAULT_VISIBLE = 4;
|
||||
const [showAllMine, setShowAllMine] = useState(false);
|
||||
const [showAllFeatured, setShowAllFeatured] = useState(false);
|
||||
const visibleMine = showAllMine ? (myCampaigns ?? []) : (myCampaigns ?? []).slice(0, DEFAULT_VISIBLE);
|
||||
const visibleFeatured = showAllFeatured ? orderedFeaturedCampaigns : orderedFeaturedCampaigns.slice(0, DEFAULT_VISIBLE);
|
||||
|
||||
const showSkeleton = isLoading || !moderationReady;
|
||||
const activeQuery = debouncedSearch.trim();
|
||||
const totalCampaigns = campaigns?.length ?? 0;
|
||||
|
||||
return (
|
||||
<main className="min-h-screen pb-16">
|
||||
<AllCampaignsHero campaignCount={totalCampaigns} />
|
||||
<AllCampaignsHero campaignCount={visible.length} />
|
||||
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 py-10 lg:py-14 space-y-8">
|
||||
{user && myCampaigns && myCampaigns.length > 0 && (
|
||||
@@ -211,118 +235,100 @@ export function AllCampaignsPage() {
|
||||
</section>
|
||||
)}
|
||||
|
||||
{orderedFeaturedCampaigns.length > 0 && (
|
||||
<section className="space-y-5">
|
||||
{/* Unified Campaigns section.
|
||||
- Idle (no search / no sort / no country): renders the
|
||||
moderator-curated featured grid, or a chronological
|
||||
fallback if nothing is featured yet.
|
||||
- Active: renders the full search/sort/country result set. */}
|
||||
<section className="space-y-5">
|
||||
<div className="flex flex-col items-stretch gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl sm:text-3xl font-bold tracking-tight">
|
||||
{t('campaigns.home.featured')}
|
||||
{activeQuery
|
||||
? t('common.search')
|
||||
: t('campaigns.all.title')}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{t('campaigns.home.featuredDesc', { appName: config.appName })}
|
||||
{activeQuery
|
||||
? t('common.searchResultsCount', { count: visible.length })
|
||||
: t('campaigns.all.sectionTagline')}
|
||||
</p>
|
||||
</div>
|
||||
<CampaignSection
|
||||
campaigns={visibleFeatured}
|
||||
total={orderedFeaturedCampaigns.length}
|
||||
visible={DEFAULT_VISIBLE}
|
||||
showAll={showAllFeatured}
|
||||
onToggle={() => setShowAllFeatured(!showAllFeatured)}
|
||||
<DiscoverySearchToolbar
|
||||
query={searchInput}
|
||||
onQueryChange={setSearchInput}
|
||||
sort={sort}
|
||||
onSortChange={setSortFromToolbar}
|
||||
sortOptions={['top', 'new']}
|
||||
searchPlaceholderKey="campaigns.all.searchPlaceholder"
|
||||
searchAriaLabelKey="campaigns.all.searchAriaLabel"
|
||||
showHidden={isMod ? {
|
||||
value: showHidden,
|
||||
onChange: setShowHidden,
|
||||
count: hiddenCount,
|
||||
} : undefined}
|
||||
country={urlCountry}
|
||||
onCountryChange={setCountry}
|
||||
/>
|
||||
</section>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Section heading — matches the `/pledges` and `/groups` pages
|
||||
so the discovery surfaces all share the same large-bold
|
||||
section header pattern. Title switches between Search / Top /
|
||||
New based on toolbar state; tagline stays constant.
|
||||
Search input + filter button cluster on the right, paired
|
||||
with the heading on the left in a single row. */}
|
||||
<div className="flex flex-col items-stretch gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl sm:text-3xl font-bold tracking-tight">
|
||||
{activeQuery
|
||||
? t('common.search')
|
||||
: t('campaigns.all.title')}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{activeQuery
|
||||
? t('common.searchResultsCount', { count: visible.length })
|
||||
: t('campaigns.all.sectionTagline')}
|
||||
</p>
|
||||
</div>
|
||||
<DiscoverySearchToolbar
|
||||
query={searchInput}
|
||||
onQueryChange={setSearchInput}
|
||||
sort={toToolbarSort(sort)}
|
||||
onSortChange={setSortFromToolbar}
|
||||
sortOptions={['top', 'new']}
|
||||
searchPlaceholderKey="campaigns.all.searchPlaceholder"
|
||||
searchAriaLabelKey="campaigns.all.searchAriaLabel"
|
||||
showHidden={isMod ? {
|
||||
value: showHidden,
|
||||
onChange: setShowHidden,
|
||||
count: hiddenCount,
|
||||
} : undefined}
|
||||
country={urlCountry}
|
||||
onCountryChange={setCountry}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Grid — widens to 3 columns at lg and 4 at xl so desktop users
|
||||
can scan more campaigns at once, matching the Pledge index's
|
||||
card density. Mobile and small tablets stay single / double
|
||||
column so the cards keep their tappable size. */}
|
||||
{showSkeleton ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
{Array.from({ length: 8 }).map((_, i) => (
|
||||
<CampaignCardSkeleton key={i} />
|
||||
))}
|
||||
</div>
|
||||
) : visible.length === 0 ? (
|
||||
<Card className="border-dashed">
|
||||
<CardContent className="py-12 px-8 text-center space-y-4">
|
||||
<HandHeart className="size-10 text-muted-foreground mx-auto" />
|
||||
<div className="space-y-1.5">
|
||||
{activeQuery ? (
|
||||
<>
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t('campaigns.all.noMatch', { query: activeQuery })}
|
||||
</h2>
|
||||
<p className="text-muted-foreground max-w-sm mx-auto">
|
||||
{t('campaigns.all.noMatchHint')}
|
||||
</p>
|
||||
</>
|
||||
) : hiddenCount > 0 && !showHidden ? (
|
||||
<>
|
||||
<h2 className="text-lg font-semibold">{t('campaigns.all.allHidden')}</h2>
|
||||
<p className="text-muted-foreground max-w-sm mx-auto">
|
||||
{t('campaigns.all.allHiddenHint')}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h2 className="text-lg font-semibold">{t('campaigns.all.empty')}</h2>
|
||||
<p className="text-muted-foreground max-w-sm mx-auto">
|
||||
{t('campaigns.all.emptyHint')}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Button asChild>
|
||||
<Link to="/campaigns/new">
|
||||
<PlusCircle className="size-4 mr-2" />
|
||||
{t('campaigns.all.startCampaign')}
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
{visible.map((campaign) => (
|
||||
<CampaignCard key={campaign.aTag} campaign={campaign} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{/* Grid — widens to 3 columns at lg and 4 at xl so desktop
|
||||
users can scan more campaigns at once, matching the Pledge
|
||||
index's card density. Mobile and small tablets stay
|
||||
single / double column so the cards keep their tappable
|
||||
size. */}
|
||||
{showSkeleton ? (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
{Array.from({ length: 8 }).map((_, i) => (
|
||||
<CampaignCardSkeleton key={i} />
|
||||
))}
|
||||
</div>
|
||||
) : (isActive ? visible : idleCampaigns).length === 0 ? (
|
||||
<Card className="border-dashed">
|
||||
<CardContent className="py-12 px-8 text-center space-y-4">
|
||||
<HandHeart className="size-10 text-muted-foreground mx-auto" />
|
||||
<div className="space-y-1.5">
|
||||
{activeQuery ? (
|
||||
<>
|
||||
<h2 className="text-lg font-semibold">
|
||||
{t('campaigns.all.noMatch', { query: activeQuery })}
|
||||
</h2>
|
||||
<p className="text-muted-foreground max-w-sm mx-auto">
|
||||
{t('campaigns.all.noMatchHint')}
|
||||
</p>
|
||||
</>
|
||||
) : hiddenCount > 0 && !showHidden ? (
|
||||
<>
|
||||
<h2 className="text-lg font-semibold">{t('campaigns.all.allHidden')}</h2>
|
||||
<p className="text-muted-foreground max-w-sm mx-auto">
|
||||
{t('campaigns.all.allHiddenHint')}
|
||||
</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<h2 className="text-lg font-semibold">{t('campaigns.all.empty')}</h2>
|
||||
<p className="text-muted-foreground max-w-sm mx-auto">
|
||||
{t('campaigns.all.emptyHint')}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<Button asChild>
|
||||
<Link to="/campaigns/new">
|
||||
<PlusCircle className="size-4 mr-2" />
|
||||
{t('campaigns.all.startCampaign')}
|
||||
</Link>
|
||||
</Button>
|
||||
</CardContent>
|
||||
</Card>
|
||||
) : (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-5">
|
||||
{(isActive ? visible : idleCampaigns).map((campaign) => (
|
||||
<CampaignCard key={campaign.aTag} campaign={campaign} />
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
||||
{/* Moderator-only: every hidden campaign on the network. Mirrors
|
||||
the section on `/campaigns` so moderators see the same
|
||||
|
||||
@@ -65,8 +65,9 @@ export function CommunitiesPage() {
|
||||
|
||||
// On-page NIP-50 search + sort + show-hidden toolbar state.
|
||||
//
|
||||
// Default sort, empty query → curated "My groups" / "Featured" /
|
||||
// moderator shelves below.
|
||||
// Default sort, empty query → curated featured-first idle view.
|
||||
// If no groups are featured yet, falls back to the chronological
|
||||
// "all groups" grid so the page is never blank.
|
||||
// Default sort, with query → relay search for kind 34550, results
|
||||
// post-filtered against name/description/content client-side.
|
||||
// Top / New → always active. Top sends `sort:top`;
|
||||
@@ -122,9 +123,14 @@ export function CommunitiesPage() {
|
||||
}, [searchHitsRaw, orgModeration, showHidden]);
|
||||
|
||||
const { data: allOrgs, isLoading: allOrgsLoading } = useDiscoverCommunities({ limit: 200 });
|
||||
// `allGroups` is the chronological "every Agora-tagged group" list,
|
||||
// used both as the fallback for the idle view (when nothing is
|
||||
// featured yet) and as the result list when the user switches to
|
||||
// Top/New sort with no typed query. Featured groups are NOT removed
|
||||
// here — when the user is actively browsing they want the full set;
|
||||
// the idle-mode rendering path handles the featured-first framing.
|
||||
const { allGroups, allHiddenCount, hiddenGroups } = useMemo(() => {
|
||||
const hiddenCoords = orgModeration?.hiddenCoords ?? new Set<string>();
|
||||
const featuredCoords = orgModeration?.featuredCoords ?? new Set<string>();
|
||||
let hidden = 0;
|
||||
const visible: ParsedCommunity[] = [];
|
||||
const hiddenList: ParsedCommunity[] = [];
|
||||
@@ -133,20 +139,34 @@ export function CommunitiesPage() {
|
||||
hidden += 1;
|
||||
hiddenList.push(org);
|
||||
if (isMod && showHidden) visible.push(org);
|
||||
} else if (hasAgoraTag(org.tags) && !featuredCoords.has(org.aTag)) {
|
||||
} else if (hasAgoraTag(org.tags)) {
|
||||
visible.push(org);
|
||||
}
|
||||
}
|
||||
return { allGroups: visible, allHiddenCount: hidden, hiddenGroups: hiddenList };
|
||||
}, [allOrgs, isMod, orgModeration, showHidden]);
|
||||
|
||||
// Search + sort + show-hidden cluster for the All section.
|
||||
// Featured groups for the idle view. When there are none, the idle
|
||||
// view falls back to `allGroups` so users always see something.
|
||||
const { data: featuredOrgs } = useFeaturedOrganizations();
|
||||
const featuredGroups = useMemo<ParsedCommunity[]>(() => {
|
||||
if (!featuredOrgs) return [];
|
||||
const hiddenCoords = orgModeration?.hiddenCoords ?? new Set<string>();
|
||||
return featuredOrgs
|
||||
.map((entry) => entry.community)
|
||||
.filter((c) => !hiddenCoords.has(c.aTag));
|
||||
}, [featuredOrgs, orgModeration]);
|
||||
|
||||
const idleGroups = featuredGroups.length > 0 ? featuredGroups : allGroups;
|
||||
|
||||
// Search + sort + show-hidden cluster for the unified section.
|
||||
const searchToolbar = (
|
||||
<DiscoverySearchToolbar
|
||||
query={searchInput}
|
||||
onQueryChange={setSearchInput}
|
||||
sort={sortMode}
|
||||
onSortChange={setSortMode}
|
||||
sortOptions={['top', 'new']}
|
||||
searchPlaceholderKey="groups.list.searchPlaceholder"
|
||||
searchAriaLabelKey="groups.list.searchAriaLabel"
|
||||
showHidden={isMod ? {
|
||||
@@ -166,19 +186,18 @@ export function CommunitiesPage() {
|
||||
userOrganizations={userOrganizations}
|
||||
/>
|
||||
|
||||
<FeaturedOrganizationsShelf />
|
||||
|
||||
{/* Unified Groups section.
|
||||
- Idle (no search / no sort): renders moderator-featured
|
||||
groups, or the chronological "all groups" fallback when
|
||||
nothing is featured yet.
|
||||
- Active: renders the full search/sort result set. */}
|
||||
<section className="space-y-5">
|
||||
<div className="flex flex-col items-stretch gap-4 sm:flex-row sm:items-end sm:justify-between">
|
||||
<div>
|
||||
<h2 className="text-2xl sm:text-3xl font-bold tracking-tight">
|
||||
{trimmedSearch
|
||||
? t('common.search')
|
||||
: isSearching && sortMode === 'top'
|
||||
? t('common.sortTop')
|
||||
: isSearching && sortMode === 'new'
|
||||
? t('common.sortNew')
|
||||
: t('groups.list.allGroups')}
|
||||
: t('groups.list.allGroups')}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{isSearching && searchHits
|
||||
@@ -234,9 +253,9 @@ export function CommunitiesPage() {
|
||||
<CommunityMiniCardSkeleton key={i} className="w-full" />
|
||||
))}
|
||||
</CommunityGrid>
|
||||
) : allGroups.length > 0 ? (
|
||||
) : idleGroups.length > 0 ? (
|
||||
<CommunityGrid>
|
||||
{allGroups.map((community) => (
|
||||
{idleGroups.map((community) => (
|
||||
<CommunityMiniCard
|
||||
key={community.aTag}
|
||||
community={community}
|
||||
@@ -536,36 +555,6 @@ function MyCommunitiesShelf({
|
||||
);
|
||||
}
|
||||
|
||||
function FeaturedOrganizationsShelf() {
|
||||
const { data: featured } = useFeaturedOrganizations();
|
||||
if (!featured || featured.length === 0) return null;
|
||||
|
||||
return (
|
||||
<section className="space-y-5">
|
||||
<FeaturedOrganizationsHeading />
|
||||
<CommunityGrid>
|
||||
{featured.map((entry) => (
|
||||
<CommunityMiniCard
|
||||
key={entry.community.aTag}
|
||||
community={entry.community}
|
||||
className="w-full"
|
||||
/>
|
||||
))}
|
||||
</CommunityGrid>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function FeaturedOrganizationsHeading() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div>
|
||||
<h2 className="text-2xl sm:text-3xl font-bold tracking-tight">
|
||||
{t('groups.list.featuredGroups')}
|
||||
</h2>
|
||||
<p className="text-sm text-muted-foreground mt-1">
|
||||
{t('groups.list.featuredGroupsTagline')}
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user