diff --git a/index.html b/index.html index 84baee7..a36ddf5 100644 --- a/index.html +++ b/index.html @@ -14,6 +14,22 @@ RankerHub — Gamified Developer Leaderboard + + + diff --git a/src/components/dashboard/RankPreview.jsx b/src/components/dashboard/RankPreview.jsx index b228f9b..3ee2136 100644 --- a/src/components/dashboard/RankPreview.jsx +++ b/src/components/dashboard/RankPreview.jsx @@ -81,11 +81,11 @@ export const RankPreview = () => {
{/* Badge Icon */}
- {badge.name[0]} + {badge.icon}
{/* Badge Info */} diff --git a/src/constants/index.js b/src/constants/index.js index 4719c1e..fae4e20 100644 --- a/src/constants/index.js +++ b/src/constants/index.js @@ -44,9 +44,9 @@ export const uiGradients = { }; export const systemBadges = [ - { id: "b1", name: "Pioneer", description: "First 100 users", color: "from-amber-500 to-orange-500" }, - { id: "b2", name: "Code Warrior", description: "100+ contributions", color: "from-blue-500 to-indigo-500" }, - { id: "b3", name: "Streak Master", description: "10+ day streak", color: "from-red-500 to-pink-500" }, - { id: "b4", name: "CSS Sorceress", description: "UI layout designer", color: "from-purple-500 to-violet-500" }, - { id: "b5", name: "Ranker Ambassador", description: "10+ successful referrals", color: "from-emerald-500 to-teal-500" } + { id: "b1", name: "Pioneer", icon: "🦅", description: "First 100 users", color: "from-amber-500 to-orange-500" }, + { id: "b2", name: "Code Warrior", icon: "⚔️", description: "100+ contributions", color: "from-blue-500 to-indigo-500" }, + { id: "b3", name: "Streak Master", icon: "🔥", description: "10+ day streak", color: "from-red-500 to-pink-500" }, + { id: "b4", name: "CSS Sorceress", icon: "🎨", description: "UI layout designer", color: "from-purple-500 to-violet-500" }, + { id: "b5", name: "Ranker Ambassador", icon: "🦈", description: "10+ successful referrals", color: "from-emerald-500 to-teal-500" } ]; diff --git a/src/pages/Achievements.jsx b/src/pages/Achievements.jsx index 0033101..b9a92d7 100644 --- a/src/pages/Achievements.jsx +++ b/src/pages/Achievements.jsx @@ -173,7 +173,7 @@ export const Achievements = () => { !info.unlocked ? "brightness-75 saturate-50" : "" }`} > - {badge.name[0]} + {badge.icon} {/* Locked / Unlocked Indicator */}
@@ -311,7 +311,7 @@ export const Achievements = () => {
{/* Glowing Big Badge representation */}
- {selectedBadge.name[0]} + {selectedBadge.icon} {/* Orbit Ring */}
diff --git a/src/services/friendsService.js b/src/services/friendsService.js index 1630c1e..44e21db 100644 --- a/src/services/friendsService.js +++ b/src/services/friendsService.js @@ -9,7 +9,9 @@ import { orderBy, documentId, writeBatch, - increment + increment, + deleteDoc, + setDoc } from "firebase/firestore"; import { db } from "../lib/firebase"; @@ -113,36 +115,37 @@ export const toggleFollowStatus = async (currentUserId, developerId, isFollowing const followDocId = `${currentUserId}_${developerId}`; const followRef = doc(db, "follows", followDocId); - // 1. Initialize atomic write batch - const batch = writeBatch(db); - - // 2. Select a randomized shard document to spread lock contention uniformly - const randomShardId = Math.floor(Math.random() * SHARD_COUNT).toString(); - const globalConnectionsShardRef = doc(db, "aggregates", "global_connections", "shards", randomShardId); - + // 1. Perform the core follow/unfollow write on its own. + // This must succeed independently of the analytics shard counter below, + // otherwise a permission error on the shard write silently blocks the + // entire unfollow action (the original batch.commit() would throw and + // roll back, including the delete, with no visible error to the user). try { if (isFollowing) { // Unfollow - batch.delete(followRef); - // Decrement the distributed shard counter atomically - batch.set(globalConnectionsShardRef, { count: increment(-1) }, { merge: true }); + await deleteDoc(followRef); } else { // Follow - batch.set(followRef, { + await setDoc(followRef, { followerId: currentUserId, followedId: developerId, createdAt: new Date().toISOString() }); - // Increment the distributed shard counter atomically - batch.set(globalConnectionsShardRef, { count: increment(1) }, { merge: true }); } - - // Commit all updates atomically in a single network transaction write - await batch.commit(); } catch (error) { - console.error("Error toggling follow status with batch:", error); + console.error("Error toggling follow status:", error); throw error; } + + // 2. Best-effort update of the distributed shard counter for global stats. + // Failures here should not affect the user's follow/unfollow state. + try { + const randomShardId = Math.floor(Math.random() * SHARD_COUNT).toString(); + const globalConnectionsShardRef = doc(db, "aggregates", "global_connections", "shards", randomShardId); + await setDoc(globalConnectionsShardRef, { count: increment(isFollowing ? -1 : 1) }, { merge: true }); + } catch (error) { + console.error("Error updating connection shard counter:", error); + } }; /**