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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ yarn-error.log*
*.tsbuildinfo
next-env.d.ts

supabase/
# supabase
supabase/*
!supabase/migrations


yarn.lock
pnpm-lock.yaml
Expand Down
68 changes: 68 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,39 @@
Measure and share your coding productivity with personalized leaderboards. Compare your progress with peers while keeping full control over privacy and leaderboard settings.

## Getting Started
Install the dependencies:

```bash
npm install
```

## Supabase

First by creating a supabase cloud project:

- go to [Supabase Dashboard](https://app.supabase.com)
- click `New Project`
- choose:
- Organization → (create one if needed)
- Project Name → e.g. devpulse-waka
- Database Password → choose a secure one
- Region → pick the nearest location
- Click Create new project
- Wait a few moments for the database to be provisioned.

## Setup Environment

Copy the .env.example to .env

```bash
cp .env.example .env

# Open .env and fill in the values for:
# NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
# NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
```

## Development

First, run the development server:

Expand All @@ -14,6 +47,41 @@ npm run dev

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

## Database Migrations

Login first (if you haven't):

```bash
npx supabase login
```

Link the cloud project to this local one:

```bash
npx supabase link
```

It'll show the list of project you have select your project.

Push migrations to cloud:

```bash
npx supabase db push
```

Pull migrations from cloud:

```bash
npx supabase db pull
```

Updating types (if you ever changed migrations):

```bash
npx supabase gen types typescript --project-id <project-id> --schema public > app/supabase-types.ts
```


## Learn More

To learn more about Next.js, take a look at the following resources:
Expand Down
5 changes: 1 addition & 4 deletions app/(public)/join/[code]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@ async function getLeaderboard(code: string) {
return data;
}



export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { code } = await params;
const leaderboard = await getLeaderboard(code);
Expand All @@ -31,7 +29,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {

const title = `You're invited to join ${leaderboard.name}!`;
const description =
leaderboard.description?.length > 0
leaderboard?.description && leaderboard.description.length > 0
? leaderboard.description
: `Join the ${leaderboard.name} leaderboard on DevPulse and compete with other developers. Track your coding activity and climb the ranks!`;

Expand All @@ -57,4 +55,3 @@ export default async function JoinPage({ params }: Props) {
const { code } = await params;
redirect(`/join?id=${encodeURIComponent(code)}`);
}

68 changes: 55 additions & 13 deletions app/(public)/join/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export async function generateMetadata({

const title = `You're invited to join ${leaderboard.name}!`;
const description =
leaderboard.description?.length > 0
leaderboard.description && leaderboard.description?.length > 0
? leaderboard.description
: `Join the ${leaderboard.name} leaderboard on DevPulse and compete with other developers. Track your coding activity and climb the ranks!`;

Expand Down Expand Up @@ -96,9 +96,12 @@ export default async function JoinPage({ searchParams }: Props) {
/>
</svg>
</div>
<h1 className="text-xl font-bold text-white mb-2">Join a Leaderboard</h1>
<h1 className="text-xl font-bold text-white mb-2">
Join a Leaderboard
</h1>
<p className="text-gray-400 text-sm mb-6">
Open an invite link like <span className="font-mono">/join?id=XXXXXXXX</span>.
Open an invite link like{" "}
<span className="font-mono">/join?id=XXXXXXXX</span>.
</p>
<Link href="/" className="btn-primary inline-block px-6 py-3 text-sm">
Go to DevPulse
Expand All @@ -115,11 +118,23 @@ export default async function JoinPage({ searchParams }: Props) {
<div className="min-h-screen flex items-center justify-center bg-[#0a0a1a] text-white grid-bg">
<div className="glass-card p-10 text-center max-w-md mx-auto">
<div className="w-16 h-16 rounded-full bg-red-500/10 border border-red-500/20 flex items-center justify-center mx-auto mb-5">
<svg className="w-8 h-8 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
<svg
className="w-8 h-8 text-red-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</div>
<h1 className="text-xl font-bold text-white mb-2">Invite Not Found</h1>
<h1 className="text-xl font-bold text-white mb-2">
Invite Not Found
</h1>
<p className="text-gray-400 text-sm mb-6">
This invite link is invalid or has expired.
</p>
Expand Down Expand Up @@ -163,7 +178,9 @@ export default async function JoinPage({ searchParams }: Props) {
</div>

<p className="text-xs uppercase tracking-[0.2em] text-indigo-400 font-semibold mb-3">
{alreadyMember ? "You\u2019re already a member of" : "You\u2019ve been invited to"}
{alreadyMember
? "You\u2019re already a member of"
: "You\u2019ve been invited to"}
</p>

<h1 className="text-2xl md:text-3xl font-bold gradient-text mb-2">
Expand All @@ -178,14 +195,36 @@ export default async function JoinPage({ searchParams }: Props) {

<div className="flex items-center justify-center gap-6 mt-6 mb-8">
<div className="flex items-center gap-2 text-gray-400 text-sm">
<svg className="w-4 h-4 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z" />
<svg
className="w-4 h-4 text-indigo-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
<span>{memberCount} {memberCount === 1 ? "member" : "members"}</span>
<span>
{memberCount} {memberCount === 1 ? "member" : "members"}
</span>
</div>
<div className="flex items-center gap-2 text-gray-400 text-sm">
<svg className="w-4 h-4 text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
<svg
className="w-4 h-4 text-purple-400"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
/>
</svg>
<span>Leaderboard</span>
</div>
Expand All @@ -201,7 +240,10 @@ export default async function JoinPage({ searchParams }: Props) {
{!user && (
<p className="text-[11px] text-gray-600 mt-6">
Powered by{" "}
<Link href="/" className="text-indigo-400/70 hover:text-indigo-400 transition-colors">
<Link
href="/"
className="text-indigo-400/70 hover:text-indigo-400 transition-colors"
>
DevPulse
</Link>{" "}
&mdash; Track your coding activity &amp; compete
Expand Down
4 changes: 2 additions & 2 deletions app/(public)/leaderboard/[slug]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createClient } from "../../../lib/supabase/server";
import LeaderboardTable from "../../../components/LeaderboardTable";
import LeaderboardTable, { NonNullableMember } from "../../../components/LeaderboardTable";
import LeaderboardHeader from "@/app/components/leaderboard/Header";
import BackButton from "@/app/components/leaderboard/BackButton";
import Footer from "@/app/components/layout/Footer";
Expand Down Expand Up @@ -56,7 +56,7 @@ export default async function LeaderboardPage(props: {
<BackButton />
<LeaderboardHeader leaderboard={leaderboard} isOwner={isOwner} />
<LeaderboardTable
members={members || []}
members={members as NonNullableMember[]}
ownerId={user?.id}
/>
</div>
Expand Down
13 changes: 10 additions & 3 deletions app/components/Chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ export default function Chat({ user }: { user: User }) {

return {
id: convo.id,
users: convo.users.map((u) => ({
users: convo.users.map((u: { user_id: string; email: string }) => ({
id: u.user_id,
email: u.email,
})),
Expand Down Expand Up @@ -210,7 +210,14 @@ export default function Chat({ user }: { user: User }) {
.from("top_user_stats")
.select("user_id, email")
.neq("user_id", user.id);
if (data) setAllUsers(data);
if (data) {
const users: ChatUser[] = data.filter(
(u): u is { user_id: string; email: string } =>
u.user_id !== null && u.email !== null,
);

setAllUsers(users);
}
};

fetchUsers();
Expand Down Expand Up @@ -354,7 +361,7 @@ export default function Chat({ user }: { user: User }) {
<p className="text-gray-300 text-[15px] font-bold mb-1.5 tracking-tight">
No Conversation Selected
</p>
<p className="text-gray-500 text-xs max-w-[200px] leading-relaxed">
<p className="text-gray-500 text-xs max-w-50 leading-relaxed">
Select a conversation from the top or start a new one.
</p>
</div>
Expand Down
Loading
Loading