fix: persist like counts across refreshes and remove social links card from non-RAISE centers
This commit is contained in:
@@ -150,10 +150,37 @@ export default function Events() {
|
|||||||
return { totalCount: CLUBS_DATA.length, centersCount: centers, clubsCount: clubs };
|
return { totalCount: CLUBS_DATA.length, centersCount: centers, clubsCount: clubs };
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// ─── Database-Backed Interactive Like System ─────────────────────────────
|
// ─── Database & LocalStorage Persisted Interactive Like System ───────────
|
||||||
|
const loadSavedLikesMap = (): Record<string, number> => {
|
||||||
|
const initialMap: Record<string, number> = {};
|
||||||
|
CLUBS_DATA.forEach((c) => {
|
||||||
|
initialMap[c.id] = 0;
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
const savedCounts = localStorage.getItem('rit_freshers_likes_count_v3');
|
||||||
|
if (savedCounts) {
|
||||||
|
const parsed = JSON.parse(savedCounts);
|
||||||
|
Object.assign(initialMap, parsed);
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const savedLikedList = localStorage.getItem('rit_freshers_liked_clubs_v3');
|
||||||
|
if (savedLikedList) {
|
||||||
|
const likedArray: string[] = JSON.parse(savedLikedList);
|
||||||
|
likedArray.forEach((id) => {
|
||||||
|
if (!initialMap[id] || initialMap[id] < 1) {
|
||||||
|
initialMap[id] = 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
return initialMap;
|
||||||
|
};
|
||||||
|
|
||||||
const [likedClubs, setLikedClubs] = useState<Set<string>>(() => {
|
const [likedClubs, setLikedClubs] = useState<Set<string>>(() => {
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem('rit_freshers_liked_clubs');
|
|
||||||
const saved = localStorage.getItem('rit_freshers_liked_clubs_v3');
|
const saved = localStorage.getItem('rit_freshers_liked_clubs_v3');
|
||||||
return saved ? new Set(JSON.parse(saved)) : new Set();
|
return saved ? new Set(JSON.parse(saved)) : new Set();
|
||||||
} catch {
|
} catch {
|
||||||
@@ -161,38 +188,38 @@ export default function Events() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const [likesMap, setLikesMap] = useState<Record<string, number>>(() => {
|
const [likesMap, setLikesMap] = useState<Record<string, number>>(loadSavedLikesMap);
|
||||||
const initialMap: Record<string, number> = {};
|
|
||||||
CLUBS_DATA.forEach((c) => {
|
|
||||||
initialMap[c.id] = 0;
|
|
||||||
});
|
|
||||||
return initialMap;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sync likes with database backend on mount and when user identity changes
|
// Sync likes with database backend on mount and when user identity changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
|
|
||||||
async function syncWithDatabase() {
|
async function syncWithDatabase() {
|
||||||
// Fetch total like counts for all clubs from database
|
// 1. Fetch total like counts for all clubs from database
|
||||||
const dbLikesMap = await fetchClubLikes();
|
const dbLikesMap = await fetchClubLikes();
|
||||||
if (isMounted && Object.keys(dbLikesMap).length > 0) {
|
if (isMounted && Object.keys(dbLikesMap).length > 0) {
|
||||||
setLikesMap((prev) => {
|
setLikesMap((prev) => {
|
||||||
const merged = { ...prev };
|
const merged = { ...prev };
|
||||||
Object.entries(dbLikesMap).forEach(([id, count]) => {
|
Object.entries(dbLikesMap).forEach(([id, count]) => {
|
||||||
merged[id] = count;
|
merged[id] = Math.max(merged[id] || 0, Number(count) || 0);
|
||||||
});
|
});
|
||||||
|
try {
|
||||||
|
localStorage.setItem('rit_freshers_likes_count_v3', JSON.stringify(merged));
|
||||||
|
} catch {}
|
||||||
return merged;
|
return merged;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch user's liked clubs from database
|
// 2. Fetch user's liked clubs from database
|
||||||
const userLikedList = await fetchUserLikedClubs(userIdentifier);
|
const userLikedList = await fetchUserLikedClubs(userIdentifier);
|
||||||
if (isMounted && userLikedList.length > 0) {
|
if (isMounted && userLikedList && userLikedList.length > 0) {
|
||||||
setLikedClubs(new Set(userLikedList));
|
setLikedClubs((prev) => {
|
||||||
try {
|
const mergedSet = new Set([...Array.from(prev), ...userLikedList]);
|
||||||
localStorage.setItem('rit_freshers_liked_clubs_v3', JSON.stringify(userLikedList));
|
try {
|
||||||
} catch {}
|
localStorage.setItem('rit_freshers_liked_clubs_v3', JSON.stringify(Array.from(mergedSet)));
|
||||||
|
} catch {}
|
||||||
|
return mergedSet;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,32 +233,53 @@ export default function Events() {
|
|||||||
const toggleLike = async (clubId: string) => {
|
const toggleLike = async (clubId: string) => {
|
||||||
const isCurrentlyLiked = likedClubs.has(clubId);
|
const isCurrentlyLiked = likedClubs.has(clubId);
|
||||||
const next = new Set(likedClubs);
|
const next = new Set(likedClubs);
|
||||||
|
let newCount = likesMap[clubId] || 0;
|
||||||
|
|
||||||
// Optimistic UI update
|
// Optimistic UI update
|
||||||
if (isCurrentlyLiked) {
|
if (isCurrentlyLiked) {
|
||||||
next.delete(clubId);
|
next.delete(clubId);
|
||||||
setLikesMap((l) => ({ ...l, [clubId]: Math.max(0, (l[clubId] || 1) - 1) }));
|
newCount = Math.max(0, newCount - 1);
|
||||||
} else {
|
} else {
|
||||||
next.add(clubId);
|
next.add(clubId);
|
||||||
setLikesMap((l) => ({ ...l, [clubId]: (l[clubId] || 0) + 1 }));
|
newCount = newCount + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updatedLikesMap = { ...likesMap, [clubId]: newCount };
|
||||||
setLikedClubs(next);
|
setLikedClubs(next);
|
||||||
|
setLikesMap(updatedLikesMap);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
localStorage.setItem('rit_freshers_liked_clubs_v3', JSON.stringify(Array.from(next)));
|
localStorage.setItem('rit_freshers_liked_clubs_v3', JSON.stringify(Array.from(next)));
|
||||||
|
localStorage.setItem('rit_freshers_likes_count_v3', JSON.stringify(updatedLikesMap));
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
// Persist like directly in PostgreSQL database backend
|
// Persist like directly in PostgreSQL database backend if available
|
||||||
const dbResult = await toggleClubLikeInDb(clubId, userIdentifier);
|
const dbResult = await toggleClubLikeInDb(clubId, userIdentifier);
|
||||||
if (dbResult) {
|
if (dbResult) {
|
||||||
setLikesMap((l) => ({ ...l, [clubId]: dbResult.count }));
|
setLikesMap((l) => {
|
||||||
|
const freshMap = { ...l, [clubId]: dbResult.count };
|
||||||
|
try {
|
||||||
|
localStorage.setItem('rit_freshers_likes_count_v3', JSON.stringify(freshMap));
|
||||||
|
} catch {}
|
||||||
|
return freshMap;
|
||||||
|
});
|
||||||
|
|
||||||
if (dbResult.liked) {
|
if (dbResult.liked) {
|
||||||
setLikedClubs((prev) => new Set([...prev, clubId]));
|
setLikedClubs((prev) => {
|
||||||
|
const s = new Set([...Array.from(prev), clubId]);
|
||||||
|
try {
|
||||||
|
localStorage.setItem('rit_freshers_liked_clubs_v3', JSON.stringify(Array.from(s)));
|
||||||
|
} catch {}
|
||||||
|
return s;
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
setLikedClubs((prev) => {
|
setLikedClubs((prev) => {
|
||||||
const updated = new Set(prev);
|
const s = new Set(prev);
|
||||||
updated.delete(clubId);
|
s.delete(clubId);
|
||||||
return updated;
|
try {
|
||||||
|
localStorage.setItem('rit_freshers_liked_clubs_v3', JSON.stringify(Array.from(s)));
|
||||||
|
} catch {}
|
||||||
|
return s;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -929,15 +977,14 @@ export default function Events() {
|
|||||||
{/* Social & Community Links */}
|
{/* Social & Community Links */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const isRaiseIncubator = selectedClub.id === 'center_raise' || selectedClub.socialLinks?.website === '/raise';
|
const isRaiseIncubator = selectedClub.id === 'center_raise' || selectedClub.socialLinks?.website === '/raise';
|
||||||
const hasSocials = Boolean(
|
const hasSpecificSocials = Boolean(
|
||||||
selectedClub.socialLinks?.instagram ||
|
selectedClub.socialLinks?.instagram ||
|
||||||
selectedClub.socialLinks?.linkedin ||
|
selectedClub.socialLinks?.linkedin ||
|
||||||
selectedClub.socialLinks?.whatsapp ||
|
selectedClub.socialLinks?.whatsapp ||
|
||||||
selectedClub.socialLinks?.youtube ||
|
selectedClub.socialLinks?.youtube
|
||||||
selectedClub.socialLinks?.website
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasSocials) return null;
|
if (!isRaiseIncubator && !hasSpecificSocials) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-6 bg-orange-50/60 border border-orange-200/80 rounded-2xl p-4">
|
<div className="mb-6 bg-orange-50/60 border border-orange-200/80 rounded-2xl p-4">
|
||||||
@@ -950,9 +997,9 @@ export default function Events() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-2.5">
|
<div className="grid grid-cols-2 gap-2.5">
|
||||||
{isRaiseIncubator && selectedClub.socialLinks?.website && (
|
{isRaiseIncubator && (
|
||||||
<Link
|
<Link
|
||||||
to={selectedClub.socialLinks.website}
|
to="/raise"
|
||||||
onClick={() => setSelectedClub(null)}
|
onClick={() => setSelectedClub(null)}
|
||||||
className="flex items-center gap-2 p-2.5 rounded-xl bg-gradient-to-r from-[#EC4899] via-[#F43F5E] to-[#E11D48] text-white hover:from-pink-600 hover:to-rose-600 transition-all text-xs font-bold shadow-xs col-span-2 justify-center"
|
className="flex items-center gap-2 p-2.5 rounded-xl bg-gradient-to-r from-[#EC4899] via-[#F43F5E] to-[#E11D48] text-white hover:from-pink-600 hover:to-rose-600 transition-all text-xs font-bold shadow-xs col-span-2 justify-center"
|
||||||
>
|
>
|
||||||
@@ -961,69 +1008,57 @@ export default function Events() {
|
|||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!isRaiseIncubator && selectedClub.socialLinks?.website && selectedClub.socialLinks.website.startsWith('http') && (
|
{!isRaiseIncubator && selectedClub.socialLinks?.instagram && (
|
||||||
<a
|
<a
|
||||||
href={selectedClub.socialLinks.website}
|
href={selectedClub.socialLinks.instagram}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-2 p-2.5 rounded-xl bg-white border border-slate-200 hover:border-orange-500 hover:text-orange-600 transition-all text-xs font-medium text-slate-700"
|
className="flex items-center gap-2 p-2.5 rounded-xl bg-white border border-slate-200 hover:border-pink-500 hover:text-pink-600 transition-all text-xs font-medium text-slate-700"
|
||||||
>
|
>
|
||||||
<Globe className="w-4 h-4 text-orange-500 shrink-0" />
|
<InstagramIcon className="w-4 h-4 text-pink-500 shrink-0" />
|
||||||
<span className="truncate">Official Website</span>
|
<span className="truncate">Instagram</span>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedClub.socialLinks?.instagram && (
|
{!isRaiseIncubator && selectedClub.socialLinks?.linkedin && (
|
||||||
<a
|
<a
|
||||||
href={selectedClub.socialLinks.instagram}
|
href={selectedClub.socialLinks.linkedin}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-2 p-2.5 rounded-xl bg-white border border-slate-200 hover:border-pink-500 hover:text-pink-600 transition-all text-xs font-medium text-slate-700"
|
className="flex items-center gap-2 p-2.5 rounded-xl bg-white border border-slate-200 hover:border-blue-600 hover:text-blue-600 transition-all text-xs font-medium text-slate-700"
|
||||||
>
|
>
|
||||||
<InstagramIcon className="w-4 h-4 text-pink-500 shrink-0" />
|
<LinkedinIcon className="w-4 h-4 text-blue-600 shrink-0" />
|
||||||
<span className="truncate">Instagram</span>
|
<span className="truncate">LinkedIn</span>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedClub.socialLinks?.linkedin && (
|
{!isRaiseIncubator && selectedClub.socialLinks?.whatsapp && (
|
||||||
<a
|
<a
|
||||||
href={selectedClub.socialLinks.linkedin}
|
href={selectedClub.socialLinks.whatsapp}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-2 p-2.5 rounded-xl bg-white border border-slate-200 hover:border-blue-600 hover:text-blue-600 transition-all text-xs font-medium text-slate-700"
|
className="flex items-center gap-2 p-2.5 rounded-xl bg-white border border-slate-200 hover:border-emerald-500 hover:text-emerald-600 transition-all text-xs font-medium text-slate-700"
|
||||||
>
|
>
|
||||||
<LinkedinIcon className="w-4 h-4 text-blue-600 shrink-0" />
|
<MessageCircle className="w-4 h-4 text-emerald-500 shrink-0" />
|
||||||
<span className="truncate">LinkedIn</span>
|
<span className="truncate">WhatsApp Group</span>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{selectedClub.socialLinks?.whatsapp && (
|
{!isRaiseIncubator && selectedClub.socialLinks?.youtube && (
|
||||||
<a
|
<a
|
||||||
href={selectedClub.socialLinks.whatsapp}
|
href={selectedClub.socialLinks.youtube}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="flex items-center gap-2 p-2.5 rounded-xl bg-white border border-slate-200 hover:border-emerald-500 hover:text-emerald-600 transition-all text-xs font-medium text-slate-700"
|
className="flex items-center gap-2 p-2.5 rounded-xl bg-white border border-slate-200 hover:border-red-600 hover:text-red-600 transition-all text-xs font-medium text-slate-700"
|
||||||
>
|
>
|
||||||
<MessageCircle className="w-4 h-4 text-emerald-500 shrink-0" />
|
<YoutubeIcon className="w-4 h-4 text-red-600 shrink-0" />
|
||||||
<span className="truncate">WhatsApp Group</span>
|
<span className="truncate">YouTube Channel</span>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
{selectedClub.socialLinks?.youtube && (
|
|
||||||
<a
|
|
||||||
href={selectedClub.socialLinks.youtube}
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="flex items-center gap-2 p-2.5 rounded-xl bg-white border border-slate-200 hover:border-red-600 hover:text-red-600 transition-all text-xs font-medium text-slate-700"
|
|
||||||
>
|
|
||||||
<YoutubeIcon className="w-4 h-4 text-red-600 shrink-0" />
|
|
||||||
<span className="truncate">YouTube Channel</span>
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
);
|
||||||
);
|
})()}
|
||||||
})()}
|
|
||||||
|
|
||||||
{/* Close Button */}
|
{/* Close Button */}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user