Skip to content

Commit 7123716

Browse files
committed
Team Member Settings Page updates
1 parent 96b41c9 commit 7123716

File tree

6 files changed

+128
-78
lines changed

6 files changed

+128
-78
lines changed

apps/dashboard/src/@/api/team-members.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import "server-only";
2-
import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
32
import { API_SERVER_URL } from "@/constants/env";
4-
import { cookies } from "next/headers";
3+
import { getAuthToken } from "../../app/api/lib/getAuthToken";
54

65
const TeamAccountRole = {
76
OWNER: "OWNER",
@@ -26,14 +25,10 @@ export type TeamMember = {
2625
};
2726

2827
export async function getMembers(teamSlug: string) {
29-
const cookiesManager = cookies();
30-
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
31-
const token = activeAccount
32-
? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
33-
: null;
28+
const token = getAuthToken();
3429

3530
if (!token) {
36-
return [];
31+
return undefined;
3732
}
3833

3934
const teamsRes = await fetch(
@@ -44,8 +39,10 @@ export async function getMembers(teamSlug: string) {
4439
},
4540
},
4641
);
42+
4743
if (teamsRes.ok) {
4844
return (await teamsRes.json())?.result as TeamMember[];
4945
}
50-
return [];
46+
47+
return undefined;
5148
}

apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/members/InviteSection.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ export function InviteSection(props: {
2525
}) {
2626
const teamPlan = getValidTeamPlan(props.team);
2727
let bottomSection: React.ReactNode = null;
28-
const inviteEnabled = teamPlan === "pro" && props.userHasEditPermission;
28+
const inviteEnabled = teamPlan !== "free" && props.userHasEditPermission;
2929

30-
if (teamPlan !== "pro") {
30+
if (teamPlan === "free") {
3131
bottomSection = (
3232
<div className="lg:px6 flex items-center justify-between gap-4 border-border border-t px-4 py-4">
3333
<p className="text-muted-foreground text-sm">
@@ -37,7 +37,7 @@ export function InviteSection(props: {
3737
target="_blank"
3838
className="text-link-foreground hover:text-foreground"
3939
>
40-
Pro plan <ExternalLinkIcon className="inline size-3" />
40+
Growth plan <ExternalLinkIcon className="inline size-3" />
4141
</Link>
4242
</p>
4343

apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/members/ManageMembersSection.tsx

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ import {
1414
SelectValue,
1515
} from "@/components/ui/select";
1616
import { EllipsisIcon, SearchIcon } from "lucide-react";
17-
import { useState } from "react";
17+
import { useMemo, useState } from "react";
18+
19+
type RoleFilterValue = "ALL ROLES" | TeamAccountRole;
1820

1921
export function ManageMembersSection(props: {
2022
team: Team;
@@ -23,6 +25,36 @@ export function ManageMembersSection(props: {
2325
}) {
2426
let topSection: React.ReactNode = null;
2527

28+
const [role, setRole] = useState<RoleFilterValue>("ALL ROLES");
29+
const [sortBy, setSortBy] = useState<MemberSortId>("date");
30+
31+
const membersToShow = useMemo(() => {
32+
let value = props.members;
33+
if (role !== "ALL ROLES") {
34+
value = value.filter((m) => m.role === role);
35+
}
36+
37+
switch (sortBy) {
38+
case "date":
39+
value = value.sort(
40+
(a, b) => a.createdAt.getTime() - b.createdAt.getTime(),
41+
);
42+
break;
43+
case "a-z":
44+
value = value.sort((a, b) =>
45+
a.account.name.localeCompare(b.account.name),
46+
);
47+
break;
48+
case "z-a":
49+
value = value.sort((a, b) =>
50+
b.account.name.localeCompare(a.account.name),
51+
);
52+
break;
53+
}
54+
55+
return value;
56+
}, [role, props.members, sortBy]);
57+
2658
if (!props.userHasEditPermission) {
2759
topSection = (
2860
<div className="border-border border-b p-4">
@@ -39,22 +71,22 @@ export function ManageMembersSection(props: {
3971
id="select-all"
4072
className="border-muted-foreground data-[state=checked]:border-inverted"
4173
disabled={
42-
!props.userHasEditPermission || props.members.length === 0
74+
!props.userHasEditPermission || membersToShow.length === 0
4375
}
4476
/>
4577
<Label
4678
htmlFor="select-all"
4779
className="cursor-pointer text-muted-foreground"
4880
>
49-
Select All ({props.members.length})
81+
Select All ({membersToShow.length})
5082
</Label>
5183
</div>
5284

5385
<Button
5486
size="icon"
5587
variant="ghost"
5688
className="!h-auto !w-auto p-1.5"
57-
disabled={!props.userHasEditPermission || props.members.length === 0}
89+
disabled={!props.userHasEditPermission || membersToShow.length === 0}
5890
>
5991
<EllipsisIcon className="size-4 text-muted-foreground" />
6092
</Button>
@@ -68,7 +100,14 @@ export function ManageMembersSection(props: {
68100

69101
<div className="h-3" />
70102

71-
<FiltersSection disabled={props.members.length === 0} />
103+
<FiltersSection
104+
// don't use membersToShow here
105+
disabled={props.members.length === 0}
106+
role={role}
107+
setRole={setRole}
108+
setSortBy={setSortBy}
109+
sortBy={sortBy}
110+
/>
72111

73112
<div className="h-4" />
74113

@@ -77,11 +116,14 @@ export function ManageMembersSection(props: {
77116
{/* Top section */}
78117
{topSection}
79118

80-
{props.members.length > 0 && (
119+
{membersToShow.length > 0 && (
81120
<ul>
82-
{props.members.map((member) => {
121+
{membersToShow.map((member) => {
83122
return (
84-
<li key={member.accountId}>
123+
<li
124+
key={member.accountId}
125+
className="border-border border-b last:border-b-0"
126+
>
85127
<MemberRow
86128
member={member}
87129
userHasEditPermission={props.userHasEditPermission}
@@ -93,7 +135,7 @@ export function ManageMembersSection(props: {
93135
)}
94136

95137
{/* Empty state */}
96-
{props.members.length === 0 && (
138+
{membersToShow.length === 0 && (
97139
<div className="flex justify-center px-4 py-10">
98140
<p className="text-muted-foreground text-sm">No Members Found</p>
99141
</div>
@@ -108,7 +150,7 @@ function MemberRow(props: {
108150
userHasEditPermission: boolean;
109151
}) {
110152
return (
111-
<div className="flex items-center justify-between border-border border-b px-4 py-4">
153+
<div className="flex items-center justify-between px-4 py-4">
112154
<div className="flex items-center gap-3 lg:gap-4">
113155
{/* Checkbox */}
114156
<Checkbox
@@ -150,7 +192,12 @@ function MemberRow(props: {
150192

151193
function FiltersSection(props: {
152194
disabled: boolean;
195+
role: RoleFilterValue;
196+
setRole: (role: RoleFilterValue) => void;
197+
setSortBy: (sortBy: MemberSortId) => void;
198+
sortBy: MemberSortId;
153199
}) {
200+
const { role, setRole, setSortBy, sortBy } = props;
154201
return (
155202
<div className="flex flex-col gap-4 lg:flex-row lg:items-center">
156203
{/* Search */}
@@ -164,8 +211,12 @@ function FiltersSection(props: {
164211
</div>
165212

166213
<div className="grid grid-cols-2 items-center gap-3 lg:flex">
167-
<RoleSelector disabled={props.disabled} />
168-
<SortMembersBy disabled={props.disabled} />
214+
<RoleSelector disabled={props.disabled} role={role} setRole={setRole} />
215+
<SortMembersBy
216+
disabled={props.disabled}
217+
setSortBy={setSortBy}
218+
sortBy={sortBy}
219+
/>
169220
</div>
170221
</div>
171222
);
@@ -175,8 +226,10 @@ type MemberSortId = "date" | "a-z" | "z-a";
175226

176227
function SortMembersBy(props: {
177228
disabled?: boolean;
229+
setSortBy: (sortBy: MemberSortId) => void;
230+
sortBy: MemberSortId;
178231
}) {
179-
const [sortBy, setSortBy] = useState<MemberSortId>("date");
232+
const { sortBy, setSortBy } = props;
180233
const valueToLabel: Record<MemberSortId, string> = {
181234
date: "Date",
182235
"a-z": "Name (A-Z)",
@@ -211,19 +264,17 @@ function SortMembersBy(props: {
211264

212265
function RoleSelector(props: {
213266
disabled?: boolean;
267+
role: RoleFilterValue;
268+
setRole: (role: RoleFilterValue) => void;
214269
}) {
215-
const roles: (TeamAccountRole | "ALL ROLES")[] = [
216-
"OWNER",
217-
"MEMBER",
218-
"ALL ROLES",
219-
];
220-
const [role, setRole] = useState<TeamAccountRole | "ALL ROLES">("ALL ROLES");
270+
const { role, setRole } = props;
271+
const roles: RoleFilterValue[] = ["OWNER", "MEMBER", "ALL ROLES"];
221272

222273
return (
223274
<Select
224275
value={role}
225276
onValueChange={(v) => {
226-
setRole(v as TeamAccountRole);
277+
setRole(v as RoleFilterValue);
227278
}}
228279
>
229280
<SelectTrigger

apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/members/TeamMembersSettingsPage.stories.tsx

Lines changed: 24 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import type { Team } from "@/api/team";
21
import type { TeamAccountRole, TeamMember } from "@/api/team-members";
32
import { Toaster } from "@/components/ui/sonner";
43
import type { Meta, StoryObj } from "@storybook/react";
4+
import { teamStub } from "../../../../../../../stories/stubs";
55
import {
66
BadgeContainer,
77
mobileViewport,
@@ -34,37 +34,22 @@ export const Mobile: Story = {
3434
},
3535
};
3636

37-
const freeTeam: Team = {
38-
id: "team-id-foo-bar",
39-
name: "Team XYZ",
40-
slug: "team-slug-foo-bar",
41-
createdAt: "2023-07-07T19:21:33.604Z",
42-
updatedAt: "2024-07-11T00:01:02.241Z",
43-
billingStatus: "validPayment",
44-
billingPlan: "free",
45-
billingEmail: "[email protected]",
46-
};
47-
48-
const proTeam: Team = {
49-
id: "team-id-foo-bar",
50-
name: "Team XYZ",
51-
slug: "team-slug-foo-bar",
52-
createdAt: "2023-07-07T19:21:33.604Z",
53-
updatedAt: "2024-07-11T00:01:02.241Z",
54-
billingStatus: "validPayment",
55-
billingPlan: "pro",
56-
billingEmail: "[email protected]",
57-
};
37+
const freeTeam = teamStub("foo", "free");
38+
const proTeam = teamStub("bar", "pro");
39+
const growthTeam = teamStub("bazz", "growth");
5840

59-
function createMemberStub(id: string, role: TeamAccountRole): TeamMember {
41+
function createMemberStub(
42+
id: string,
43+
role: TeamAccountRole,
44+
createdHours: number,
45+
): TeamMember {
6046
const date = new Date();
61-
// add random time to the date
62-
date.setHours(Math.floor(Math.random() * 24));
47+
date.setHours(createdHours);
6348

6449
const member: TeamMember = {
6550
account: {
6651
email: `user-${id}@foo.com`,
67-
name: `username-${id}`,
52+
name: id,
6853
},
6954
accountId: `account-id-${id}`,
7055
createdAt: date,
@@ -78,9 +63,9 @@ function createMemberStub(id: string, role: TeamAccountRole): TeamMember {
7863
}
7964

8065
const membersStub: TeamMember[] = [
81-
createMemberStub("1", "OWNER"),
82-
createMemberStub("2", "MEMBER"),
83-
createMemberStub("3", "OWNER"),
66+
createMemberStub("first-member", "OWNER", 1),
67+
createMemberStub("third-member", "MEMBER", 3),
68+
createMemberStub("second-member", "OWNER", 2),
8469
];
8570

8671
function Story() {
@@ -107,7 +92,7 @@ function CompVariants() {
10792

10893
{/* Invite */}
10994
<div className="flex flex-col gap-10">
110-
<BadgeContainer label="Not a Pro Team">
95+
<BadgeContainer label="Free Team">
11196
<InviteSection team={freeTeam} userHasEditPermission={false} />
11297
</BadgeContainer>
11398

@@ -118,35 +103,32 @@ function CompVariants() {
118103
<BadgeContainer label="Pro, User has permission">
119104
<InviteSection team={proTeam} userHasEditPermission={true} />
120105
</BadgeContainer>
106+
107+
<BadgeContainer label="Growth, User has permission">
108+
<InviteSection team={growthTeam} userHasEditPermission={true} />
109+
</BadgeContainer>
121110
</div>
122111

123112
<div className="my-10" />
124113

125-
{/* Invite */}
114+
<h2 className="py-4 font-semibold text-3xl">Team Members Variants</h2>
115+
126116
<div className="flex flex-col gap-10">
127-
<BadgeContainer label="Pro Team, has permission">
117+
<BadgeContainer label="Has permission">
128118
<ManageMembersSection
129-
team={proTeam}
119+
team={freeTeam}
130120
userHasEditPermission={true}
131121
members={membersStub}
132122
/>
133123
</BadgeContainer>
134124

135-
<BadgeContainer label="Not a Pro Team, No permission">
125+
<BadgeContainer label="No permission">
136126
<ManageMembersSection
137127
team={freeTeam}
138128
userHasEditPermission={false}
139129
members={membersStub}
140130
/>
141131
</BadgeContainer>
142-
143-
<BadgeContainer label="Pro Team, No permission">
144-
<ManageMembersSection
145-
team={proTeam}
146-
userHasEditPermission={false}
147-
members={membersStub}
148-
/>
149-
</BadgeContainer>
150132
</div>
151133
</div>
152134
</div>

apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/members/TeamMembersSettingsPage.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import type { Team } from "@/api/team";
22
import type { TeamMember } from "@/api/team-members";
3+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
4+
import { AlertCircleIcon } from "lucide-react";
35
import { InviteSection } from "./InviteSection";
46
import { ManageMembersSection } from "./ManageMembersSection";
57

@@ -10,6 +12,17 @@ export function TeamMembersSettingsPage(props: {
1012
}) {
1113
return (
1214
<div>
15+
<Alert variant="info">
16+
<AlertCircleIcon className="size-5 text-red-400" />
17+
<AlertTitle>
18+
Inviting and Managing Team Members is not available yet
19+
</AlertTitle>
20+
<AlertDescription>
21+
This feature will be available in Q4 2024
22+
</AlertDescription>
23+
</Alert>
24+
<div className="h-10" />
25+
1326
<InviteSection
1427
team={props.team}
1528
userHasEditPermission={props.userHasEditPermission}

0 commit comments

Comments
 (0)