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: 9 additions & 7 deletions app/(public)/leaderboard/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import Image from "next/image";
export default async function Leaderboards() {
const supabase = await createClient();

const { data, error } = await supabase
.from("leaderboards")
.select("id, name, slug")
.order("created_at", { ascending: false });
const [leaderboardsResult, userResult] = await Promise.all([
supabase
.from("leaderboards")
.select("id, name, slug")
.order("created_at", { ascending: false }),
supabase.auth.getUser(),
]);

const {
data: { user },
} = await supabase.auth.getUser();
const { data, error } = leaderboardsResult;
const { data: user } = userResult;

if (error) {
return (
Expand Down
22 changes: 13 additions & 9 deletions app/components/dashboard/LeaderbordList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,20 @@ export default async function LeaderboardsList() {

if (!user) return null;

const { data: owned } = await supabase
.from("leaderboards")
.select("id, name, slug, owner_id")
.eq("owner_id", user.id);
const [ownResult, joinedResult] = await Promise.all([
supabase
.from("leaderboards")
.select("id, name, slug, owner_id")
.eq("owner_id", user.id),
supabase
.from("leaderboard_members")
.select("leaderboards(id, name, slug, owner_id)")
.eq("user_id", user.id)
.eq("role", "member"),
]);

const { data: joined } = await supabase
.from("leaderboard_members")
.select("leaderboards(id, name, slug, owner_id)")
.eq("user_id", user.id)
.eq("role", "member");
const owned = ownResult.data || [];
const joined = joinedResult.data || [];

const joinedBoards =
joined?.flatMap((j) => j.leaderboards) || [];
Expand Down
7 changes: 2 additions & 5 deletions app/components/landing-page/LosserMembers.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
export interface LosserMember {
email: string;
total_seconds: number;
}
import { TopMember } from "./TopLeaderbord";

export default async function LosserMembers({
losser_members,
}: {
losser_members: LosserMember[];
losser_members: TopMember[];
}) {
return (
<>
Expand Down
12 changes: 12 additions & 0 deletions app/components/landing-page/TopLeaderbord.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
export interface Category {
name: string;
text: string;
hours: number;
decimal: string;
digital: string;
minutes: number;
percent: number;
total_seconds: number;
}

export interface TopMember {
email: string;
total_seconds: number;
categories?: Category[];
}

export default function TopLeaderboard({
Expand Down
62 changes: 62 additions & 0 deletions app/components/landing-page/VibeCoders.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { TopMember } from "./TopLeaderbord";

export default async function VibeCoders({
vibe_coders,
}: {
vibe_coders: TopMember[];
}) {
return (
<>
{vibe_coders && vibe_coders.length > 0 && (
<section className="max-w-5xl mx-auto px-6 pb-5 relative z-10">
<div
className="glass-card border border-white/5 bg-white/[0.02] backdrop-blur-xl p-8 md:p-12 rounded-3xl"
data-aos="fade-up"
>
<h2 className="text-2xl font-bold text-white mb-4">
Vibe Coders Leaderboard
</h2>
<p className="text-gray-400 text-sm mb-8">
Meet the vibe coders of the week - those who spent the least
amount of time coding and more time prompting.
</p>
{(!vibe_coders || vibe_coders.length) === 0 && (
<div className="text-gray-500 text-sm italic">
No vibe coders found... 😜
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
{vibe_coders.map(
(
member: { email: string; total_seconds: number },
i: number,
) => (
<div
key={i}
className="stat-card flex justify-between items-center px-6 py-4 group bg-black/20 hover:bg-white/5 transition-all border border-white/5 rounded-xl rounded-tl-sm"
data-aos="fade-up"
data-aos-delay={(i * 50).toString()}
>
<div className="flex items-center gap-3">
<div className="w-2 h-2 rounded-full bg-emerald-400 group-hover:shadow-[0_0_10px_rgba(16,185,129,0.8)] transition-all" />
<span className="text-gray-200 font-semibold group-hover:text-white transition">
{member.email.split("@")[0]}
</span>
</div>
<span className="text-gray-500 text-sm group-hover:text-emerald-400 transition flex items-center gap-2">
{Math.floor(member.total_seconds / 3600)}h{" "}
{Math.floor((member.total_seconds % 3600) / 60)}m
</span>
</div>
),
)}
</div>
<span className="text-gray-400 text-xs mt-4 block">
Note: These are the time spent on AI Coding activities.
</span>
</div>
</section>
)}
</>
);
}
88 changes: 58 additions & 30 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,45 +6,72 @@ import CTA from "./components/layout/CTA";
import Contributors from "./components/landing-page/Contributors";
import LosserMembers from "./components/landing-page/LosserMembers";
import RecentLeaderboard from "./components/landing-page/RecentLeaderboard";
import TopLeaderboard, { TopMember } from "./components/landing-page/TopLeaderbord";
import TopLeaderboard, {
TopMember,
} from "./components/landing-page/TopLeaderbord";
import ContributeCard from "./components/landing-page/ContributeCard";
import VibeCoders from "./components/landing-page/VibeCoders";

export default async function Home() {
const supabase = await createClient();

const { data: leaderboards } = await supabase
.from("leaderboards")
.select("id, name, slug")
.order("created_at", { ascending: false })
.limit(5);
const [leaderboardsRes, losserMembersRes, topMembersRes] = await Promise.all([
supabase
.from("leaderboards")
.select("id, name, slug")
.order("created_at", { ascending: false })
.limit(5),
supabase
.from("top_user_stats")
.select("*")
.lt("total_seconds", 14400) // thats 4 hours
.neq("total_seconds", 0)
.not("total_seconds", "is", null)
.order("total_seconds", { ascending: true }),
supabase
.from("top_user_stats")
.select("*")
.order("total_seconds", { ascending: false })
.limit(100),
]);

const { data: losser_members } = await supabase
.from("top_user_stats")
.select("*")
.lt("total_seconds", 14400) // thats 4 hours
.neq("total_seconds", 0)
.not("total_seconds", "is", null)
.order("total_seconds", { ascending: true });
const leaderboards = leaderboardsRes.data ?? [];
const losser_members = losserMembersRes.data ?? [];
const top_members = topMembersRes.data ?? [];

const { data: top_members } = await supabase
.from("top_user_stats")
.select("*")
.order("total_seconds", { ascending: false })
.limit(3);
const topMembers: TopMember[] = top_members
? top_members.filter(
(u): u is { email: string; total_seconds: number; user_id: string } =>
u.email !== null && u.total_seconds !== null && u.user_id !== null,
)
: [];

const topMembers: TopMember[] = top_members ? top_members.filter(
(u): u is { email: string; total_seconds: number; user_id: string } =>
u.email !== null &&
u.total_seconds !== null &&
u.user_id !== null
) : [];
const losserMembers: TopMember[] = losser_members
? losser_members.filter(
(u): u is { email: string; total_seconds: number; user_id: string } =>
u.email !== null && u.total_seconds !== null && u.user_id !== null,
)
: [];

const losserMembers: TopMember[] = losser_members ? losser_members.filter(
(u): u is { email: string; total_seconds: number; user_id: string } =>
u.email !== null &&
u.total_seconds !== null &&
u.user_id !== null
) : [];
const topVibeCoders: TopMember[] = topMembers
.filter((member): member is TopMember =>
member.categories
? member.categories?.some(
(cat) => cat.name === "AI Coding" && cat.total_seconds > 0,
) &&
member.email !== null &&
member.total_seconds !== null
: false,
)
.map((member) => {
const codingCategory = member.categories
? member.categories.find((cat) => cat.name === "AI Coding")
: undefined;

return codingCategory
? { ...member, total_seconds: codingCategory.total_seconds }
: member;
});

return (
<div className="min-h-screen bg-[#0a0a1a] text-white overflow-hidden grid-bg relative">
Expand Down Expand Up @@ -341,6 +368,7 @@ export default async function Home() {
<TopLeaderboard top_members={topMembers ?? []} />
<RecentLeaderboard leaderboards={leaderboards ?? []} />
<LosserMembers losser_members={losserMembers ?? []} />
<VibeCoders vibe_coders={topVibeCoders ?? []} />
<Contributors />
<CTA />
<ContributeCard />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE OR REPLACE VIEW top_user_stats AS
SELECT
us.user_id,
us.total_seconds,
a.email,
us.categories -- new column
FROM user_stats us
JOIN auth.users a ON us.user_id = a.id;
Loading