Skip to content

Commit aacdaf3

Browse files
Merge branch 'main' into set-up-dashboard-db-migrations-e2b-1753
2 parents dd82db1 + 588cfbf commit aacdaf3

File tree

4 files changed

+105
-1
lines changed

4 files changed

+105
-1
lines changed

src/app/dashboard/route.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { PROTECTED_URLS } from '@/configs/urls'
3+
import { cookies } from 'next/headers'
4+
import { COOKIE_KEYS } from '@/configs/keys'
5+
import { supabaseAdmin } from '@/lib/clients/supabase/admin'
6+
import { createRouteClient } from '@/lib/clients/supabase/server'
7+
8+
const TAB_URL_MAP: Record<string, (teamId: string) => string> = {
9+
sandboxes: (teamId) => PROTECTED_URLS.SANDBOXES(teamId),
10+
templates: (teamId) => PROTECTED_URLS.TEMPLATES(teamId),
11+
usage: (teamId) => PROTECTED_URLS.USAGE(teamId),
12+
billing: (teamId) => PROTECTED_URLS.BILLING(teamId),
13+
budget: (teamId) => PROTECTED_URLS.BUDGET(teamId),
14+
keys: (teamId) => PROTECTED_URLS.KEYS(teamId),
15+
team: (teamId) => PROTECTED_URLS.TEAM(teamId),
16+
account: (_) => PROTECTED_URLS.ACCOUNT_SETTINGS,
17+
}
18+
19+
export async function GET(request: NextRequest) {
20+
// 1. Get the tab parameter
21+
const searchParams = request.nextUrl.searchParams
22+
const tab = searchParams.get('tab')
23+
24+
if (!tab || !TAB_URL_MAP[tab]) {
25+
// Default to dashboard if no valid tab
26+
return NextResponse.redirect(new URL(PROTECTED_URLS.DASHBOARD, request.url))
27+
}
28+
29+
// 2. Create Supabase client and get user
30+
const supabase = createRouteClient(request)
31+
32+
const { data, error } = await supabase.auth.getUser()
33+
34+
if (error || !data.user) {
35+
// Redirect to sign-in if not authenticated
36+
return NextResponse.redirect(new URL('/sign-in', request.url))
37+
}
38+
const cookieStore = await cookies()
39+
40+
// 3. Resolve team ID (first try cookie, then fetch default)
41+
let teamId = cookieStore.get(COOKIE_KEYS.SELECTED_TEAM_ID)?.value
42+
let teamSlug = cookieStore.get(COOKIE_KEYS.SELECTED_TEAM_SLUG)?.value
43+
44+
if (!teamId) {
45+
// No team in cookie, fetch user's default team
46+
const { data: teamsData } = await supabaseAdmin
47+
.from('users_teams')
48+
.select(
49+
`
50+
team_id,
51+
is_default,
52+
team:teams(*)
53+
`
54+
)
55+
.eq('user_id', data.user.id)
56+
57+
if (!teamsData?.length) {
58+
// No teams, redirect to new team creation
59+
return NextResponse.redirect(
60+
new URL(PROTECTED_URLS.NEW_TEAM, request.url)
61+
)
62+
}
63+
64+
// Use default team or first team
65+
const defaultTeam = teamsData.find((t) => t.is_default) || teamsData[0]
66+
teamId = defaultTeam.team_id
67+
teamSlug = defaultTeam.team?.slug || defaultTeam.team_id
68+
}
69+
70+
// 4. Build the redirect URL using the tab mapping
71+
const urlGenerator = TAB_URL_MAP[tab]
72+
const redirectPath = urlGenerator(teamSlug || teamId)
73+
74+
// 5. Redirect to the appropriate dashboard section
75+
return NextResponse.redirect(new URL(redirectPath, request.url))
76+
}

src/configs/urls.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ export const PROTECTED_URLS = {
1616
SANDBOXES: (teamId: string) => `/dashboard/${teamId}/sandboxes`,
1717
TEMPLATES: (teamId: string) => `/dashboard/${teamId}/templates`,
1818
USAGE: (teamId: string) => `/dashboard/${teamId}/usage`,
19+
BILLING: (teamId: string) => `/dashboard/${teamId}/billing`,
20+
BUDGET: (teamId: string) => `/dashboard/${teamId}/budget`,
21+
KEYS: (teamId: string) => `/dashboard/${teamId}/keys`,
1922
}
2023

2124
export const BASE_URL = process.env.VERCEL_URL

src/server/middleware.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ export async function resolveTeamForDashboard(
2222
redirect?: string
2323
allowAccess?: boolean
2424
}> {
25+
// Check for tab query parameter - skip default redirects if present
26+
const hasTabParam = request.nextUrl.searchParams.has('tab')
27+
2528
if (request.nextUrl.pathname === PROTECTED_URLS.NEW_TEAM) {
2629
return { allowAccess: true }
2730
}
@@ -62,6 +65,19 @@ export async function resolveTeamForDashboard(
6265
(await kv.get<string>(KV_KEYS.TEAM_ID_TO_SLUG(currentTeamId))) ||
6366
undefined
6467

68+
// Skip redirect if we're at /dashboard with a tab parameter
69+
if (
70+
hasTabParam &&
71+
request.nextUrl.pathname === PROTECTED_URLS.DASHBOARD
72+
) {
73+
return {
74+
teamId: currentTeamId,
75+
teamSlug,
76+
// No redirect here - we'll let the page handle the tab parameter
77+
// This case is handled by @/app/dashboard/route.ts
78+
}
79+
}
80+
6581
return {
6682
teamId: currentTeamId,
6783
teamSlug,
@@ -96,6 +112,15 @@ export async function resolveTeamForDashboard(
96112

97113
const defaultTeam = teamsData.find((t) => t.is_default) || teamsData[0]
98114

115+
// Skip redirect if we're at /dashboard with a tab parameter
116+
if (hasTabParam && request.nextUrl.pathname === PROTECTED_URLS.DASHBOARD) {
117+
return {
118+
teamId: defaultTeam.team_id,
119+
teamSlug: defaultTeam.team?.slug || undefined,
120+
// No redirect here - we'll let the page handle the tab parameter
121+
}
122+
}
123+
99124
return {
100125
teamId: defaultTeam.team_id,
101126
teamSlug: defaultTeam.team?.slug || undefined,

src/server/team/team-actions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,7 @@ export const uploadTeamProfilePictureAction = guard(
272272
const currentFileName = fileName
273273

274274
// List all files in the team's folder from Supabase Storage
275-
const folderPath = `profile-pictures/teams/${teamId}`
275+
const folderPath = `teams/${teamId}`
276276
const files = await getFiles(folderPath)
277277

278278
// Delete all old profile pictures except the one we just uploaded

0 commit comments

Comments
 (0)