Skip to content

Commit 7fa5be5

Browse files
committed
[Dashboard] Add staff mode for viewing teams without membership (#7299)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added a "STAFF MODE" warning banner for users viewing a team they do not belong to, with a button to leave staff mode. - **User Interface** - Adjusted the team header layout for improved accessibility, ensuring the team badge is no longer nested inside the team link. <!-- end of auto-generated comment: release notes by coderabbit.ai --> <!-- start pr-codex --> --- ## PR-Codex overview This PR primarily focuses on updating the `TeamHeaderUI` component and enhancing the `TeamLayout` and `ProjectLayout` components to incorporate a "Staff Mode" feature, which restricts user actions based on team membership. ### Detailed summary - Modified the structure of the `TeamHeaderUI` component to wrap the `Link` inside a `span`. - Added a "Staff Mode" warning message in `TeamLayout` if the user is not part of the team. - Implemented the same "Staff Mode" warning in `ProjectLayout`. - Updated API calls to include `getTeamBySlug` and `getProject`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 2510c4f commit 7fa5be5

File tree

3 files changed

+65
-40
lines changed

3 files changed

+65
-40
lines changed

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/layout.tsx

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import { getProjects } from "@/api/projects";
2-
import { getTeams } from "@/api/team";
2+
import { getTeamBySlug, getTeams } from "@/api/team";
33
import { AppFooter } from "@/components/blocks/app-footer";
4+
import { Button } from "@/components/ui/button";
45
import { TabPathLinks } from "@/components/ui/tabs";
56
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
67
import { AnnouncementBanner } from "components/notices/AnnouncementBanner";
8+
import Link from "next/link";
79
import { redirect } from "next/navigation";
810
import { siwaExamplePrompts } from "../../../(dashboard)/support/page";
911
import { CustomChatButton } from "../../../../nebula-app/(app)/components/CustomChat/CustomChatButton";
@@ -20,25 +22,18 @@ export default async function TeamLayout(props: {
2022
}) {
2123
const params = await props.params;
2224

23-
const [accountAddress, account, teams, authToken] = await Promise.all([
25+
const [accountAddress, account, teams, authToken, team] = await Promise.all([
2426
getAuthTokenWalletAddress(),
2527
getValidAccount(`/team/${params.team_slug}`),
2628
getTeams(),
2729
getAuthToken(),
30+
getTeamBySlug(params.team_slug),
2831
]);
2932

30-
if (!teams || !accountAddress || !authToken) {
33+
if (!teams || !accountAddress || !authToken || !team) {
3134
redirect("/login");
3235
}
3336

34-
const team = teams.find(
35-
(t) => t.slug === decodeURIComponent(params.team_slug),
36-
);
37-
38-
if (!team) {
39-
redirect("/team");
40-
}
41-
4237
const teamsAndProjects = await Promise.all(
4338
teams.map(async (team) => ({
4439
team,
@@ -53,6 +48,21 @@ export default async function TeamLayout(props: {
5348

5449
return (
5550
<div className="flex h-full grow flex-col">
51+
{!teams.some((t) => t.slug === team.slug) && (
52+
<div className="bg-warning-text">
53+
<div className="container flex items-center justify-between py-4">
54+
<div className="flex flex-col gap-2">
55+
<p className="font-bold text-white text-xl">👀 STAFF MODE 👀</p>
56+
<p className="text-sm text-white">
57+
You can only view this team, not take any actions.
58+
</p>
59+
</div>
60+
<Button variant="default" asChild>
61+
<Link href="/team/~">Leave Staff Mode</Link>
62+
</Button>
63+
</div>
64+
</div>
65+
)}
5666
<AnnouncementBanner />
5767
<div className="bg-card">
5868
<TeamHeaderLoggedIn

apps/dashboard/src/app/(app)/team/[team_slug]/[project_slug]/(sidebar)/layout.tsx

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
import { getProjects } from "@/api/projects";
2-
import { getTeams } from "@/api/team";
1+
import { getProject, getProjects } from "@/api/projects";
2+
import { getTeamBySlug, getTeams } from "@/api/team";
3+
import { Button } from "@/components/ui/button";
34
import { SidebarProvider } from "@/components/ui/sidebar";
45
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
56
import { AnnouncementBanner } from "components/notices/AnnouncementBanner";
7+
import Link from "next/link";
68
import { redirect } from "next/navigation";
79
import { siwaExamplePrompts } from "../../../../(dashboard)/support/page";
810
import { CustomChatButton } from "../../../../../nebula-app/(app)/components/CustomChat/CustomChatButton";
@@ -21,21 +23,20 @@ export default async function ProjectLayout(props: {
2123
params: Promise<{ team_slug: string; project_slug: string }>;
2224
}) {
2325
const params = await props.params;
24-
const [accountAddress, teams, account, authToken] = await Promise.all([
25-
getAuthTokenWalletAddress(),
26-
getTeams(),
27-
getValidAccount(`/team/${params.team_slug}/${params.project_slug}`),
28-
getAuthToken(),
29-
]);
26+
const [accountAddress, teams, account, authToken, team, project] =
27+
await Promise.all([
28+
getAuthTokenWalletAddress(),
29+
getTeams(),
30+
getValidAccount(`/team/${params.team_slug}/${params.project_slug}`),
31+
getAuthToken(),
32+
getTeamBySlug(params.team_slug),
33+
getProject(params.team_slug, params.project_slug),
34+
]);
3035

3136
if (!teams || !accountAddress || !authToken) {
3237
redirect("/login");
3338
}
3439

35-
const team = teams.find(
36-
(t) => t.slug === decodeURIComponent(params.team_slug),
37-
);
38-
3940
if (!team) {
4041
redirect("/team");
4142
}
@@ -47,10 +48,6 @@ export default async function ProjectLayout(props: {
4748
})),
4849
);
4950

50-
const project = teamsAndProjects
51-
.find((t) => t.team.slug === decodeURIComponent(params.team_slug))
52-
?.projects.find((p) => p.slug === params.project_slug);
53-
5451
if (!project) {
5552
// not a valid project, redirect back to team page
5653
redirect(`/team/${params.team_slug}`);
@@ -65,6 +62,21 @@ export default async function ProjectLayout(props: {
6562
return (
6663
<SidebarProvider>
6764
<div className="flex h-dvh min-w-0 grow flex-col">
65+
{!teams.some((t) => t.slug === team.slug) && (
66+
<div className="bg-warning-text">
67+
<div className="container flex items-center justify-between py-4">
68+
<div className="flex flex-col gap-2">
69+
<p className="font-bold text-white text-xl">👀 STAFF MODE 👀</p>
70+
<p className="text-sm text-white">
71+
You can only view this team, not take any actions.
72+
</p>
73+
</div>
74+
<Button variant="default" asChild>
75+
<Link href="/team/~">Leave Staff Mode</Link>
76+
</Button>
77+
</div>
78+
</div>
79+
)}
6880
<div className="sticky top-0 z-10 border-border border-b bg-card">
6981
<AnnouncementBanner />
7082
<TeamHeaderLoggedIn

apps/dashboard/src/app/(app)/team/components/TeamHeader/TeamHeaderUI.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,20 +54,23 @@ export function TeamHeaderDesktopUI(props: TeamHeaderCompProps) {
5454
<SlashSeparator />
5555

5656
<div className="flex items-center gap-1">
57-
<Link
58-
href={`/team/${currentTeam.slug}`}
59-
className="flex flex-row items-center gap-2 font-normal text-sm"
60-
>
61-
<GradientAvatar
62-
id={currentTeam.id}
63-
src={currentTeam.image || ""}
64-
className="size-6"
65-
client={props.client}
66-
/>
67-
<span> {currentTeam.name} </span>
68-
<TeamVerifiedIcon domain={currentTeam.verifiedDomain} />
57+
<span className="flex flex-row items-center gap-2 font-normal text-sm">
58+
<Link
59+
href={`/team/${currentTeam.slug}`}
60+
className="flex flex-row items-center gap-2 font-normal text-sm"
61+
>
62+
<GradientAvatar
63+
id={currentTeam.id}
64+
src={currentTeam.image || ""}
65+
className="size-6"
66+
client={props.client}
67+
/>
68+
<span> {currentTeam.name} </span>
69+
<TeamVerifiedIcon domain={currentTeam.verifiedDomain} />
70+
</Link>
71+
{/* may render its own link so has to be outside of the link */}
6972
<TeamPlanBadge plan={teamPlan} teamSlug={currentTeam.slug} />
70-
</Link>
73+
</span>
7174

7275
<TeamAndProjectSelectorPopoverButton
7376
currentProject={props.currentProject}

0 commit comments

Comments
 (0)