Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@
<link href="https://fonts.googleapis.com/css2?family=Bruno+Ace+SC&family=Montserrat:ital,wght@0,100..900;1,100..900&family=Mystery+Quest&family=Sedgwick+Ave+Display&family=Outfit:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">

<title>RankerHub — Gamified Developer Leaderboard</title>

<!-- Apply theme before first paint to prevent flash of light/dark mode on refresh -->
<script>
(function () {
try {
var savedTheme = localStorage.getItem("theme");
var theme = savedTheme || (window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light");
if (theme === "dark") {
document.documentElement.classList.add("dark");
document.documentElement.style.colorScheme = "dark";
} else {
document.documentElement.style.colorScheme = "light";
}
} catch (e) {}
})();
</script>
</head>

<body>
Expand Down
4 changes: 2 additions & 2 deletions src/components/dashboard/RankPreview.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,11 @@ export const RankPreview = () => {
<div className="flex items-center gap-3 flex-1 min-w-0">
{/* Badge Icon */}
<div
className={`w-10 h-10 rounded-lg bg-gradient-to-br ${badge.color} flex items-center justify-center text-xs font-bold text-white flex-shrink-0 ${
className={`w-10 h-10 rounded-lg bg-gradient-to-br ${badge.color} flex items-center justify-center text-lg flex-shrink-0 ${
!info.unlocked ? "brightness-75 saturate-50" : ""
}`}
>
{badge.name[0]}
{badge.icon}
</div>

{/* Badge Info */}
Expand Down
10 changes: 5 additions & 5 deletions src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
];
4 changes: 2 additions & 2 deletions src/pages/Achievements.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ export const Achievements = () => {
!info.unlocked ? "brightness-75 saturate-50" : ""
}`}
>
<span className="text-xl font-black text-white uppercase">{badge.name[0]}</span>
<span className="text-2xl">{badge.icon}</span>

{/* Locked / Unlocked Indicator */}
<div className="absolute -bottom-1 -right-1 w-5 h-5 rounded-full bg-slate-900 border border-slate-800 flex items-center justify-center">
Expand Down Expand Up @@ -311,7 +311,7 @@ export const Achievements = () => {
<div className="flex flex-col items-center text-center space-y-4 relative z-10">
{/* Glowing Big Badge representation */}
<div className={`w-24 h-24 rounded-3xl bg-gradient-to-br ${selectedBadge.color} flex items-center justify-center shadow-xl shadow-indigo-500/10 relative`}>
<span className="text-4xl font-black text-white uppercase">{selectedBadge.name[0]}</span>
<span className="text-5xl">{selectedBadge.icon}</span>

{/* Orbit Ring */}
<div className="absolute inset-0 rounded-3xl border border-white/20 animate-pulse" />
Expand Down
39 changes: 21 additions & 18 deletions src/services/friendsService.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import {
orderBy,
documentId,
writeBatch,
increment
increment,
deleteDoc,
setDoc
} from "firebase/firestore";
import { db } from "../lib/firebase";

Expand Down Expand Up @@ -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);
}
};

/**
Expand Down
Loading