Skip to content

Commit 13844ba

Browse files
atrakhConvex, Inc.
authored andcommitted
dashboard: add redirects for vercel login query params (#38352)
GitOrigin-RevId: ae4a6358af80380d8f9599f38d2ee80eca27aa0f
1 parent 9019ea5 commit 13844ba

File tree

6 files changed

+96
-10
lines changed

6 files changed

+96
-10
lines changed

npm-packages/dashboard/src/api/projects.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,14 @@ export function useCurrentProject() {
1212
return projects?.find((p) => p.slug === projectSlug);
1313
}
1414

15-
export function useProjectById(teamId?: number, projectId?: number) {
16-
const projects = useProjects(teamId);
17-
return projects?.find((p) => p.id === projectId);
15+
export function useProjectById(projectId: number | undefined) {
16+
const { data } = useBBQuery({
17+
path: "/projects/{project_id}",
18+
pathParams: {
19+
project_id: projectId?.toString() || "",
20+
},
21+
});
22+
return data;
1823
}
1924

2025
export function useProjects(

npm-packages/dashboard/src/elements/BackupIdentifier.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export function BackupIdentifier({ backup }: { backup: BackupResponse }) {
1010
team?.id || 0,
1111
backup.sourceDeploymentId,
1212
);
13-
const project = useProjectById(team?.id, deployment?.projectId);
13+
const project = useProjectById(deployment?.projectId);
1414
return (
1515
<span className="text-xs text-content-secondary">
1616
{team?.slug}-{project?.slug}-{deployment?.name}-

npm-packages/dashboard/src/elements/DeploymentDisplay.tsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { CommandLineIcon, SignalIcon } from "@heroicons/react/20/solid";
22
import { GearIcon, GlobeIcon, Pencil2Icon } from "@radix-ui/react-icons";
33
import { useCurrentDeployment, useDeployments } from "api/deployments";
4-
import { useCurrentTeam, useTeamMembers } from "api/teams";
4+
import { useTeamMembers } from "api/teams";
55
import { useProjectById } from "api/projects";
66
import { useProfile } from "api/profile";
77
import { useRememberLastViewedDeployment } from "hooks/useLastViewed";
@@ -60,8 +60,7 @@ export function DeploymentLabel({
6060
whoseName: string | null;
6161
inline?: boolean;
6262
}) {
63-
const team = useCurrentTeam();
64-
const project = useProjectById(team?.id, deployment.projectId);
63+
const project = useProjectById(deployment.projectId);
6564
const { deployments } = useDeployments(project?.id);
6665
const hasMultipleActiveLocalDeployments =
6766
deployments !== undefined &&

npm-packages/dashboard/src/lib/ssr.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,13 +184,20 @@ const getProps: GetServerSideProps<{
184184

185185
const projectsByTeam = groupBy(projects, (p: ProjectDetails) => p.teamId);
186186

187-
const initialProjects = Object.fromEntries(
187+
const initialProjectsByTeam = Object.fromEntries(
188188
teams.map(({ id: teamId }) => [
189189
`/teams/${teamId}/projects`,
190190
projectsByTeam[teamId] ?? [],
191191
]),
192192
);
193193

194+
const initialIndividualProjects = Object.fromEntries(
195+
projects.map(({ id: projectId }) => [
196+
`/projects/${projectId}`,
197+
projects.find((p: ProjectDetails) => p.id === projectId),
198+
]),
199+
);
200+
194201
const deploymentsByProject = groupBy(
195202
deployments,
196203
(d: DeploymentResponse) => d.projectId,
@@ -204,7 +211,8 @@ const getProps: GetServerSideProps<{
204211

205212
const initialData: Record<string, object> = {
206213
"/teams": teams,
207-
...initialProjects,
214+
...initialProjectsByTeam,
215+
...initialIndividualProjects,
208216
...initialDeployments,
209217
};
210218

npm-packages/dashboard/src/pages/[[...route]].tsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { useTeams } from "api/teams";
33
import { useEffect } from "react";
44
import { Loading } from "@ui/Loading";
55
import { withAuthenticatedPage } from "lib/withAuthenticatedPage";
6+
import { useProjectById } from "api/projects";
7+
import { useSupportFormOpen } from "elements/SupportWidget";
68

79
export { getServerSideProps } from "lib/ssr";
810

@@ -26,7 +28,57 @@ function RedirectToTeam() {
2628
}
2729

2830
function Main() {
31+
const { query, push } = useRouter();
32+
const { teams } = useTeams();
33+
const [, setOpenState] = useSupportFormOpen();
34+
35+
// If vercelPath is set, we need to redirect somewhere, but we don't know
36+
// which team to redirect to. We'll redirect to the first team that is managed
37+
// by Vercel. In most cases, this will be correct.
38+
const firstVercelTeam = teams?.find((t) => t.managedBy === "vercel");
39+
if (query.vercelPath === "support") {
40+
setOpenState(true);
41+
void push(firstVercelTeam ? `/t/${firstVercelTeam.slug}` : "/");
42+
return <Loading />;
43+
}
44+
45+
if (query.vercelPath === "billing") {
46+
void push(
47+
firstVercelTeam
48+
? `/t/${firstVercelTeam.slug}/settings/billing`
49+
: "/team/settings/billing",
50+
);
51+
return <Loading />;
52+
}
53+
if (query.vercelPath === "usage") {
54+
void push(
55+
firstVercelTeam
56+
? `/t/${firstVercelTeam.slug}/settings/usage`
57+
: "/team/settings/usage",
58+
);
59+
return <Loading />;
60+
}
61+
if (query.invoiceId) {
62+
// TODO(ENG-9453): Support this.
63+
}
64+
65+
if (query.projectId) {
66+
return <RedirectToProjectById id={query.vercel_resource_id as string} />;
67+
}
2968
return <RedirectToTeam />;
3069
}
3170

71+
function RedirectToProjectById({ id }: { id: string }) {
72+
const project = useProjectById(parseInt(id));
73+
const { teams } = useTeams();
74+
const projectTeam = teams?.find((team) => team.id === project?.teamId);
75+
const router = useRouter();
76+
useEffect(() => {
77+
if (project && projectTeam) {
78+
void router.replace(`/t/${projectTeam.slug}/${project.slug}`);
79+
}
80+
}, [project, projectTeam, router]);
81+
return <Loading />;
82+
}
83+
3284
export default withAuthenticatedPage(Main);

npm-packages/dashboard/src/pages/vercel_login.tsx

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useSessionStorage } from "react-use";
22
import { useRouter } from "next/router";
33
import { useEffect } from "react";
44
import { LoadingLogo } from "@ui/Loading";
5+
import { ParsedUrlQuery } from "querystring";
56

67
/**
78
* This page handles the Vercel OAuth login flow integration with Convex's authentication system.
@@ -30,7 +31,7 @@ export default function VercelLogin() {
3031
// in session storage (so it's accessible once we're redirected back and redirect to the Auth0 login page.
3132
setVercelCode(query.code.toString());
3233
void replace(
33-
`/api/auth/login?connection=vercel${query.returnTo ? `&returnTo=${query.returnTo}` : ""}`,
34+
`/api/auth/login?connection=vercel&returnTo=${vercelReturnTo(query)}`,
3435
);
3536
}
3637

@@ -56,3 +57,24 @@ export default function VercelLogin() {
5657
</div>
5758
);
5859
}
60+
61+
function vercelReturnTo(query: ParsedUrlQuery) {
62+
const params = [];
63+
if (query.resource_id) {
64+
params.push(`projectId=${query.projectId}`);
65+
}
66+
if (query.invoice_id) {
67+
params.push(`invoiceId=${query.invoiceId}`);
68+
}
69+
if (
70+
query.path &&
71+
["billing", "support", "usage"].includes(query.path as string)
72+
) {
73+
params.push(`vercelPath=${query.path}`);
74+
}
75+
76+
if (params.length > 0) {
77+
return `/?${params.join("&")}`;
78+
}
79+
return "/";
80+
}

0 commit comments

Comments
 (0)