Skip to content

Commit 9aaba00

Browse files
committed
feat: add Supabase types and apply DB schema changes
- Add generated supabase-types.ts with DB types and helpers - Use Database generic in client/server Supabase clients - Introduce NonNullableMember and update components to use DB types - Move SQL into supabase/migrations and adjust schemas (defaults, NOT NULL) - Update .gitignore to ignore supabase/* but keep supabase/migrations - Add README setup instructions for Supabase and .env guidance
1 parent 5d6281e commit 9aaba00

20 files changed

Lines changed: 709 additions & 99 deletions

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ yarn-error.log*
4545
*.tsbuildinfo
4646
next-env.d.ts
4747

48-
supabase/
48+
# supabase
49+
supabase/*
50+
!supabase/migrations
51+
4952

5053
yarn.lock
5154
pnpm-lock.yaml

README.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,39 @@
55
Measure and share your coding productivity with personalized leaderboards. Compare your progress with peers while keeping full control over privacy and leaderboard settings.
66

77
## Getting Started
8+
Install the dependencies:
9+
10+
```bash
11+
npm install
12+
```
13+
14+
## Supabase
15+
16+
First by creating a supabase cloud project:
17+
18+
- go to [Supabase Dashboard](https://app.supabase.com)
19+
- click `New Project`
20+
- choose:
21+
- Organization → (create one if needed)
22+
- Project Name → e.g. devpulse-waka
23+
- Database Password → choose a secure one
24+
- Region → pick the nearest location
25+
- Click Create new project
26+
- Wait a few moments for the database to be provisioned.
27+
28+
## Setup Environment
29+
30+
Copy the .env.example to .env
31+
32+
```bash
33+
cp .env.example .env
34+
35+
# Open .env and fill in the values for:
36+
# NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
37+
# NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
38+
```
39+
40+
## Development
841

942
First, run the development server:
1043

@@ -14,6 +47,41 @@ npm run dev
1447

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

50+
## Database Migrations
51+
52+
Login first (if you haven't):
53+
54+
```bash
55+
npx supabase login
56+
```
57+
58+
Link the cloud project to this local one:
59+
60+
```bash
61+
npx supabase link
62+
```
63+
64+
It'll show the list of project you have select your project.
65+
66+
Push migrations to cloud:
67+
68+
```bash
69+
npx supabase db push
70+
```
71+
72+
Pull migrations from cloud:
73+
74+
```bash
75+
npx supabase db pull
76+
```
77+
78+
Updating types (if you ever changed migrations):
79+
80+
```bash
81+
npx supabase gen types typescript --project-id <project-id> --schema public > app/supabase-types.ts
82+
```
83+
84+
1785
## Learn More
1886

1987
To learn more about Next.js, take a look at the following resources:

app/(public)/join/[code]/page.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ async function getLeaderboard(code: string) {
1616
return data;
1717
}
1818

19-
20-
2119
export async function generateMetadata({ params }: Props): Promise<Metadata> {
2220
const { code } = await params;
2321
const leaderboard = await getLeaderboard(code);
@@ -31,7 +29,7 @@ export async function generateMetadata({ params }: Props): Promise<Metadata> {
3129

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

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

app/(public)/join/page.tsx

Lines changed: 55 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export async function generateMetadata({
5151

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

@@ -96,9 +96,12 @@ export default async function JoinPage({ searchParams }: Props) {
9696
/>
9797
</svg>
9898
</div>
99-
<h1 className="text-xl font-bold text-white mb-2">Join a Leaderboard</h1>
99+
<h1 className="text-xl font-bold text-white mb-2">
100+
Join a Leaderboard
101+
</h1>
100102
<p className="text-gray-400 text-sm mb-6">
101-
Open an invite link like <span className="font-mono">/join?id=XXXXXXXX</span>.
103+
Open an invite link like{" "}
104+
<span className="font-mono">/join?id=XXXXXXXX</span>.
102105
</p>
103106
<Link href="/" className="btn-primary inline-block px-6 py-3 text-sm">
104107
Go to DevPulse
@@ -115,11 +118,23 @@ export default async function JoinPage({ searchParams }: Props) {
115118
<div className="min-h-screen flex items-center justify-center bg-[#0a0a1a] text-white grid-bg">
116119
<div className="glass-card p-10 text-center max-w-md mx-auto">
117120
<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">
118-
<svg className="w-8 h-8 text-red-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
119-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
121+
<svg
122+
className="w-8 h-8 text-red-400"
123+
fill="none"
124+
viewBox="0 0 24 24"
125+
stroke="currentColor"
126+
>
127+
<path
128+
strokeLinecap="round"
129+
strokeLinejoin="round"
130+
strokeWidth={2}
131+
d="M6 18L18 6M6 6l12 12"
132+
/>
120133
</svg>
121134
</div>
122-
<h1 className="text-xl font-bold text-white mb-2">Invite Not Found</h1>
135+
<h1 className="text-xl font-bold text-white mb-2">
136+
Invite Not Found
137+
</h1>
123138
<p className="text-gray-400 text-sm mb-6">
124139
This invite link is invalid or has expired.
125140
</p>
@@ -163,7 +178,9 @@ export default async function JoinPage({ searchParams }: Props) {
163178
</div>
164179

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

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

179196
<div className="flex items-center justify-center gap-6 mt-6 mb-8">
180197
<div className="flex items-center gap-2 text-gray-400 text-sm">
181-
<svg className="w-4 h-4 text-indigo-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
182-
<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" />
198+
<svg
199+
className="w-4 h-4 text-indigo-400"
200+
fill="none"
201+
viewBox="0 0 24 24"
202+
stroke="currentColor"
203+
>
204+
<path
205+
strokeLinecap="round"
206+
strokeLinejoin="round"
207+
strokeWidth={2}
208+
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"
209+
/>
183210
</svg>
184-
<span>{memberCount} {memberCount === 1 ? "member" : "members"}</span>
211+
<span>
212+
{memberCount} {memberCount === 1 ? "member" : "members"}
213+
</span>
185214
</div>
186215
<div className="flex items-center gap-2 text-gray-400 text-sm">
187-
<svg className="w-4 h-4 text-purple-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
188-
<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" />
216+
<svg
217+
className="w-4 h-4 text-purple-400"
218+
fill="none"
219+
viewBox="0 0 24 24"
220+
stroke="currentColor"
221+
>
222+
<path
223+
strokeLinecap="round"
224+
strokeLinejoin="round"
225+
strokeWidth={2}
226+
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"
227+
/>
189228
</svg>
190229
<span>Leaderboard</span>
191230
</div>
@@ -201,7 +240,10 @@ export default async function JoinPage({ searchParams }: Props) {
201240
{!user && (
202241
<p className="text-[11px] text-gray-600 mt-6">
203242
Powered by{" "}
204-
<Link href="/" className="text-indigo-400/70 hover:text-indigo-400 transition-colors">
243+
<Link
244+
href="/"
245+
className="text-indigo-400/70 hover:text-indigo-400 transition-colors"
246+
>
205247
DevPulse
206248
</Link>{" "}
207249
&mdash; Track your coding activity &amp; compete

app/(public)/leaderboard/[slug]/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createClient } from "../../../lib/supabase/server";
2-
import LeaderboardTable from "../../../components/LeaderboardTable";
2+
import LeaderboardTable, { NonNullableMember } from "../../../components/LeaderboardTable";
33
import LeaderboardHeader from "@/app/components/leaderboard/Header";
44
import BackButton from "@/app/components/leaderboard/BackButton";
55
import Footer from "@/app/components/layout/Footer";
@@ -56,7 +56,7 @@ export default async function LeaderboardPage(props: {
5656
<BackButton />
5757
<LeaderboardHeader leaderboard={leaderboard} isOwner={isOwner} />
5858
<LeaderboardTable
59-
members={members || []}
59+
members={members as NonNullableMember[]}
6060
ownerId={user?.id}
6161
/>
6262
</div>

app/components/Chat.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export default function Chat({ user }: { user: User }) {
8585

8686
return {
8787
id: convo.id,
88-
users: convo.users.map((u) => ({
88+
users: convo.users.map((u: { user_id: string; email: string }) => ({
8989
id: u.user_id,
9090
email: u.email,
9191
})),
@@ -210,7 +210,14 @@ export default function Chat({ user }: { user: User }) {
210210
.from("top_user_stats")
211211
.select("user_id, email")
212212
.neq("user_id", user.id);
213-
if (data) setAllUsers(data);
213+
if (data) {
214+
const users: ChatUser[] = data.filter(
215+
(u): u is { user_id: string; email: string } =>
216+
u.user_id !== null && u.email !== null,
217+
);
218+
219+
setAllUsers(users);
220+
}
214221
};
215222

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

0 commit comments

Comments
 (0)