Skip to content

Commit 680b8cd

Browse files
authored
[Dashboard] Rename getMemberById to getMemberByAccountId and add onboarding banner (#7334)
1 parent 787db93 commit 680b8cd

File tree

11 files changed

+97
-46
lines changed

11 files changed

+97
-46
lines changed

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

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export async function getMembers(teamSlug: string) {
3333
}
3434

3535
const teamsRes = await fetch(
36-
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamSlug}/members`,
36+
new URL(`/v1/teams/${teamSlug}/members`, NEXT_PUBLIC_THIRDWEB_API_HOST),
3737
{
3838
headers: {
3939
Authorization: `Bearer ${token}`,
@@ -48,15 +48,21 @@ export async function getMembers(teamSlug: string) {
4848
return undefined;
4949
}
5050

51-
export async function getMemberById(teamSlug: string, memberId: string) {
51+
export async function getMemberByAccountId(
52+
teamSlug: string,
53+
accountId: string,
54+
) {
5255
const token = await getAuthToken();
5356

5457
if (!token) {
5558
return undefined;
5659
}
5760

5861
const res = await fetch(
59-
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${teamSlug}/members/${memberId}`,
62+
new URL(
63+
`/v1/teams/${teamSlug}/members/${accountId}`,
64+
NEXT_PUBLIC_THIRDWEB_API_HOST,
65+
),
6066
{
6167
headers: {
6268
Authorization: `Bearer ${token}`,

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "@/constants/public-envs";
33
import { API_SERVER_SECRET } from "@/constants/server-envs";
44
import type { TeamResponse } from "@thirdweb-dev/service-utils";
55
import { cookies } from "next/headers";
6+
import { getValidAccount } from "../../app/(app)/account/settings/getAccount";
67
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken";
78
import { LAST_USED_TEAM_ID } from "../../constants/cookies";
9+
import { getMemberByAccountId } from "./team-members";
810

911
export type Team = TeamResponse & { stripeCustomerId: string | null };
1012

@@ -103,3 +105,23 @@ export async function getLastVisitedTeam() {
103105

104106
return null;
105107
}
108+
109+
export async function hasToCompleteTeamOnboarding(
110+
team: Team,
111+
pagePath: string,
112+
) {
113+
// if the team is already onboarded, we don't need to check anything else here
114+
if (team.isOnboarded) {
115+
return false;
116+
}
117+
const account = await getValidAccount(pagePath);
118+
const teamMember = await getMemberByAccountId(team.slug, account.id);
119+
120+
// if the team member is not an owner (or we cannot find them), they cannot complete onboarding anyways
121+
if (teamMember?.role !== "OWNER") {
122+
return false;
123+
}
124+
125+
// if we get here the team is not onboarded and the team member is an owner, so we need to show the onboarding page
126+
return true;
127+
}

apps/dashboard/src/app/(app)/account/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getTeams } from "@/api/team";
2-
import { getMemberById } from "@/api/team-members";
2+
import { getMemberByAccountId } from "@/api/team-members";
33
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
44
import { notFound } from "next/navigation";
55
import { getAuthToken } from "../api/lib/getAuthToken";
@@ -25,7 +25,7 @@ export default async function Page() {
2525

2626
const teamsWithRole = await Promise.all(
2727
teams.map(async (team) => {
28-
const member = await getMemberById(team.slug, account.id);
28+
const member = await getMemberByAccountId(team.slug, account.id);
2929

3030
if (!member) {
3131
notFound();

apps/dashboard/src/app/(app)/components/TeamPlanBadge.tsx

Lines changed: 27 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Team } from "@/api/team";
44
import { Badge, type BadgeProps } from "@/components/ui/badge";
55
import { cn } from "@/lib/utils";
66
import { useTrack } from "hooks/analytics/useTrack";
7-
import Link from "next/link";
7+
import { useDashboardRouter } from "../../../@/lib/DashboardRouter";
88

99
const teamPlanToBadgeVariant: Record<
1010
Team["billingPlan"],
@@ -38,33 +38,37 @@ export function TeamPlanBadge(props: {
3838
className?: string;
3939
postfix?: string;
4040
}) {
41-
const badge = (
41+
const router = useDashboardRouter();
42+
const track = useTrack();
43+
44+
function handleNavigateToBilling(e: React.MouseEvent | React.KeyboardEvent) {
45+
if (props.plan !== "free") {
46+
return;
47+
}
48+
e.stopPropagation();
49+
e.preventDefault();
50+
track({
51+
category: "billing",
52+
action: "show_plans",
53+
label: "team_badge",
54+
});
55+
router.push(`/team/${props.teamSlug}/~/settings/billing?showPlans=true`);
56+
}
57+
58+
return (
4259
<Badge
4360
variant={teamPlanToBadgeVariant[props.plan]}
4461
className={cn("px-1.5 capitalize", props.className)}
62+
role={props.plan === "free" ? "button" : undefined}
63+
tabIndex={props.plan === "free" ? 0 : undefined}
64+
onClick={handleNavigateToBilling}
65+
onKeyDown={(e) => {
66+
if (e.key === "Enter" || e.key === " ") {
67+
handleNavigateToBilling(e);
68+
}
69+
}}
4570
>
4671
{`${getTeamPlanBadgeLabel(props.plan)}${props.postfix || ""}`}
4772
</Badge>
4873
);
49-
50-
const track = useTrack();
51-
52-
if (props.plan === "free") {
53-
return (
54-
<Link
55-
href={`/team/${props.teamSlug}/~/settings/billing?showPlans=true`}
56-
onClick={() => {
57-
track({
58-
category: "billing",
59-
action: "show_plans",
60-
label: "team_badge",
61-
});
62-
}}
63-
>
64-
{badge}
65-
</Link>
66-
);
67-
}
68-
69-
return badge;
7074
}

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { getProjects } from "@/api/projects";
22
import { getTeamBySlug, getTeams } from "@/api/team";
33
import { AppFooter } from "@/components/blocks/app-footer";
4+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
45
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
6+
import { differenceInDays } from "date-fns";
7+
import { InfoIcon } from "lucide-react";
58
import { notFound } from "next/navigation";
69
import { getValidAccount } from "../../../account/settings/getAccount";
710
import {
@@ -32,6 +35,10 @@ export default async function Layout(props: {
3235
notFound();
3336
}
3437

38+
// show the banner only if the team was created more than 3 days ago
39+
const shouldShowOnboardingBanner =
40+
differenceInDays(new Date(), new Date(team.createdAt)) > 3;
41+
3542
// Note:
3643
// Do not check that team is already onboarded or not and redirect away from /get-started pages
3744
// because the team is marked as onboarded in the first step- instead of after completing all the steps
@@ -60,6 +67,21 @@ export default async function Layout(props: {
6067
teamsAndProjects={teamsAndProjects}
6168
/>
6269
</div>
70+
{shouldShowOnboardingBanner && (
71+
<div className="container mt-10">
72+
<Alert variant="info">
73+
<InfoIcon className="size-5" />
74+
<AlertTitle>Finish setting up your team</AlertTitle>
75+
<AlertDescription>
76+
Your team predates our latest onboarding flow, so a few steps
77+
might still be pending.
78+
<br />
79+
Completing this updated guide takes less than a minute and ensures
80+
everything is set up correctly.
81+
</AlertDescription>
82+
</Alert>
83+
</div>
84+
)}
6385
{props.children}
6486
<AppFooter />
6587
</div>
Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,6 @@
1-
import type { Team } from "@/api/team";
21
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
32

43
export function isAccountOnboardingComplete(account: Account) {
54
// if email is confirmed, onboarding is considered complete
65
return !!account.emailConfirmedAt;
76
}
8-
9-
export function isTeamOnboardingComplete(team: Team) {
10-
return team.isOnboarded;
11-
}

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/settings/billing/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getStripeBalance } from "@/actions/stripe-actions";
22
import { type Team, getTeamBySlug } from "@/api/team";
3-
import { getMemberById } from "@/api/team-members";
3+
import { getMemberByAccountId } from "@/api/team-members";
44
import { getTeamSubscriptions } from "@/api/team-subscription";
55
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
66
import { redirect } from "next/navigation";
@@ -31,7 +31,7 @@ export default async function Page(props: {
3131
const [team, authToken, teamMember] = await Promise.all([
3232
getTeamBySlug(params.team_slug),
3333
getAuthToken(),
34-
getMemberById(params.team_slug, account.id),
34+
getMemberByAccountId(params.team_slug, account.id),
3535
]);
3636

3737
if (!team || !teamMember) {

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/settings/invoices/page.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getTeamInvoices } from "@/actions/stripe-actions";
22
import { getTeamBySlug } from "@/api/team";
3-
import { getMemberById } from "@/api/team-members";
3+
import { getMemberByAccountId } from "@/api/team-members";
44
import { redirect } from "next/navigation";
55
import type { SearchParams } from "nuqs/server";
66
import { getValidAccount } from "../../../../../../account/settings/getAccount";
@@ -25,7 +25,7 @@ export default async function Page(props: {
2525

2626
const [team, teamMember] = await Promise.all([
2727
getTeamBySlug(params.team_slug),
28-
getMemberById(params.team_slug, account.id),
28+
getMemberByAccountId(params.team_slug, account.id),
2929
]);
3030

3131
if (!team) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { getTeamBySlug } from "@/api/team";
2-
import { getMemberById } from "@/api/team-members";
2+
import { getMemberByAccountId } from "@/api/team-members";
33
import { checkDomainVerification } from "@/api/verified-domain";
44
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
55
import { notFound } from "next/navigation";
@@ -19,7 +19,7 @@ export default async function Page(props: {
1919

2020
const [team, teamMember, token, initialVerification] = await Promise.all([
2121
getTeamBySlug(params.team_slug),
22-
getMemberById(params.team_slug, account.id),
22+
getMemberByAccountId(params.team_slug, account.id),
2323
getAuthToken(),
2424
checkDomainVerification(params.team_slug),
2525
]);

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { getProject } from "@/api/projects";
22
import { getTeams } from "@/api/team";
3-
import { getMemberById } from "@/api/team-members";
3+
import { getMemberByAccountId } from "@/api/team-members";
44
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
55
import { notFound, redirect } from "next/navigation";
66
import { getValidAccount } from "../../../../../account/settings/getAccount";
@@ -37,7 +37,7 @@ export default async function Page(props: {
3737

3838
const teamsWithRole = await Promise.all(
3939
teams.map(async (team) => {
40-
const member = await getMemberById(team.slug, account.id);
40+
const member = await getMemberByAccountId(team.slug, account.id);
4141

4242
if (!member) {
4343
notFound();

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

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import { getTeamBySlug } from "@/api/team";
1+
import { getTeamBySlug, hasToCompleteTeamOnboarding } from "@/api/team";
22
import { PosthogIdentifierServer } from "components/wallets/PosthogIdentifierServer";
33
import { redirect } from "next/navigation";
44
import { Suspense } from "react";
55
import { getAuthToken } from "../../api/lib/getAuthToken";
66
import { EnsureValidConnectedWalletLoginServer } from "../../components/EnsureValidConnectedWalletLogin/EnsureValidConnectedWalletLoginServer";
7-
import { isTeamOnboardingComplete } from "../../login/onboarding/isOnboardingRequired";
87
import { SaveLastVisitedTeamPage } from "../components/last-visited-page/SaveLastVisitedPage";
98
import {
109
PastDueBanner,
@@ -16,8 +15,11 @@ export default async function RootTeamLayout(props: {
1615
params: Promise<{ team_slug: string }>;
1716
}) {
1817
const { team_slug } = await props.params;
19-
const authToken = await getAuthToken();
20-
const team = await getTeamBySlug(team_slug).catch(() => null);
18+
19+
const [authToken, team] = await Promise.all([
20+
getAuthToken(),
21+
getTeamBySlug(team_slug).catch(() => null),
22+
]);
2123

2224
if (!team) {
2325
redirect("/team");
@@ -27,7 +29,7 @@ export default async function RootTeamLayout(props: {
2729
redirect("/login");
2830
}
2931

30-
if (!isTeamOnboardingComplete(team)) {
32+
if (await hasToCompleteTeamOnboarding(team, `/team/${team_slug}`)) {
3133
redirect(`/get-started/team/${team.slug}`);
3234
}
3335

0 commit comments

Comments
 (0)