Skip to content

Commit 80bf795

Browse files
committed
[TOOL-4292] Dashboard: Show Team selector page for '~' team slug (#6930)
<!-- ## title your PR with this format: "[SDK/Dashboard/Portal] Feature/Fix: Concise title for the changes" If you did not copy the branch name from Linear, paste the issue tag here (format is TEAM-0000): ## Notes for the reviewer Anything important to call out? Be sure to also clarify these in your comments. ## How to test Unit tests, playground, etc. --> <!-- start pr-codex --> --- ## PR-Codex overview This PR removes a redirect feature for team paths in the `middleware.ts` file and introduces a new page component in `page.tsx` that allows users to select a team when they belong to multiple teams, enhancing the team selection experience. ### Detailed summary - Removed redirect logic for `/team/~/...` paths in `middleware.ts`. - Added a new page component in `page.tsx` for team selection. - Implemented fetching of teams and authentication token using `Promise.all`. - Included UI elements for displaying teams and their details. - Introduced `createTeamLink` function to generate team-specific links. - Utilized components like `GradientAvatar`, `TeamHeader`, and `AppFooter` for layout. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 7ae174f commit 80bf795

File tree

2 files changed

+125
-13
lines changed

2 files changed

+125
-13
lines changed
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
import { type Team, getTeams } from "@/api/team";
2+
import { GradientAvatar } from "@/components/blocks/Avatars/GradientAvatar";
3+
import { AppFooter } from "@/components/blocks/app-footer";
4+
import { DotsBackgroundPattern } from "@/components/ui/background-patterns";
5+
import { getThirdwebClient } from "@/constants/thirdweb.server";
6+
import { ChevronRightIcon, UsersIcon } from "lucide-react";
7+
import Link from "next/link";
8+
import { notFound, redirect } from "next/navigation";
9+
import { getAuthToken } from "../../../api/lib/getAuthToken";
10+
import { TeamPlanBadge } from "../../../components/TeamPlanBadge";
11+
import { TeamHeader } from "../../components/TeamHeader/team-header";
12+
13+
export default async function Page(props: {
14+
params: Promise<{
15+
paths?: string[];
16+
}>;
17+
searchParams: Promise<Record<string, string | undefined | string[]>>;
18+
}) {
19+
const [params, searchParams, teams, authToken] = await Promise.all([
20+
props.params,
21+
props.searchParams,
22+
getTeams(),
23+
getAuthToken(),
24+
]);
25+
26+
if (!teams || !authToken) {
27+
notFound();
28+
}
29+
30+
const searchParamsString = Object.entries(searchParams)
31+
.map(([key, value]) => {
32+
if (Array.isArray(value)) {
33+
return value
34+
.map((v) => `${key}=${encodeURIComponent(String(v))}`)
35+
.join("&");
36+
}
37+
return `${key}=${encodeURIComponent(String(value))}`;
38+
})
39+
.join("&");
40+
41+
// if there is a single team, redirect to the team page directly
42+
if (teams.length === 1 && teams[0]) {
43+
redirect(
44+
createTeamLink({
45+
team: teams[0],
46+
paths: params.paths,
47+
searchParams: searchParamsString,
48+
}),
49+
);
50+
}
51+
52+
const client = getThirdwebClient({
53+
jwt: authToken,
54+
teamId: undefined,
55+
});
56+
57+
return (
58+
<div className="relative flex min-h-dvh flex-col ">
59+
<div className="border-b bg-card">
60+
<TeamHeader />
61+
</div>
62+
63+
<div className="relative flex grow flex-col overflow-hidden">
64+
<DotsBackgroundPattern />
65+
<div className="container z-10 flex grow flex-col items-center justify-center py-20">
66+
<div className="w-full max-w-lg rounded-xl border bg-card shadow-2xl">
67+
<div className="flex flex-col border-b p-4 lg:p-6">
68+
<div className="mb-2 self-start rounded-full border p-2">
69+
<UsersIcon className="size-5 text-muted-foreground" />
70+
</div>
71+
<h1 className="mb-0.5 font-semibold text-xl tracking-tight">
72+
Select a team
73+
</h1>
74+
<p className="text-muted-foreground text-sm">
75+
You are currently a member of multiple teams
76+
<br />
77+
Select a team to view this page
78+
</p>
79+
</div>
80+
81+
<div className="flex flex-col [&>*:not(:last-child)]:border-b">
82+
{teams.map((team) => {
83+
return (
84+
<div
85+
key={team.id}
86+
className="group relative flex items-center gap-3 px-4 py-4 hover:bg-accent/50 lg:px-6"
87+
>
88+
<GradientAvatar
89+
src={team.image || ""}
90+
id={team.id}
91+
className="size-8 rounded-full border"
92+
client={client}
93+
/>
94+
<Link
95+
href={createTeamLink({
96+
team,
97+
paths: params.paths,
98+
searchParams: searchParamsString,
99+
})}
100+
className="before:absolute before:inset-0"
101+
>
102+
{team.name}
103+
</Link>
104+
<TeamPlanBadge plan={team.billingPlan} />
105+
<ChevronRightIcon className="ml-auto size-4 text-muted-foreground group-hover:text-foreground" />
106+
</div>
107+
);
108+
})}
109+
</div>
110+
</div>
111+
</div>
112+
</div>
113+
114+
<AppFooter className="relative z-10" />
115+
</div>
116+
);
117+
}
118+
119+
function createTeamLink(params: {
120+
team: Team;
121+
paths: string[] | undefined;
122+
searchParams: string | undefined;
123+
}) {
124+
return `/team/${params.team.slug}/${(params.paths || [])?.join("/") || ""}${params.searchParams ? `?${params.searchParams}` : ""}`;
125+
}

apps/dashboard/src/middleware.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { getDefaultTeam } from "@/api/team";
21
import { isLoginRequired } from "@/constants/auth";
32
import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
43
import { type NextRequest, NextResponse } from "next/server";
@@ -188,18 +187,6 @@ export async function middleware(request: NextRequest) {
188187
}
189188
}
190189

191-
// redirect /team/~/... to /team/<first_team_slug>/...
192-
if (paths[0] === "team" && paths[1] === "~") {
193-
const firstTeam = await getDefaultTeam();
194-
if (firstTeam) {
195-
const modifiedPaths = [...paths];
196-
modifiedPaths[1] = firstTeam.slug;
197-
return redirect(request, `/${modifiedPaths.join("/")}`, {
198-
searchParams: request.nextUrl.searchParams.toString(),
199-
cookiesToSet,
200-
});
201-
}
202-
}
203190
// END /<address>/... case
204191
// all other cases are handled by the file system router so we just fall through
205192
if (cookiesToSet) {

0 commit comments

Comments
 (0)