Skip to content

Commit 88db5ea

Browse files
committed
Add upload to IPFS on teams & account avatar form fields (#4807)
## Problem solved Short description of the bug fixed or feature added <!-- start pr-codex --> --- ## PR-Codex overview This PR focuses on enhancing the account and team settings pages by adding image upload functionality for accounts and teams, updating component imports, and improving layout responsiveness. ### Detailed summary - Added `updateAccountImage` method to `AccountSettingsPage` and `AccountSettingsPageUI`. - Introduced `updateTeamImage` method in `TeamGeneralSettingsPage` and `TeamGeneralSettingsPageUI`. - Changed layout from `min-h-full` to `min-h-screen`. - Updated import statements to reflect new component names. - Implemented async image upload functionality with IPFS integration. - Added error handling for image update methods. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 09b1c28 commit 88db5ea

File tree

10 files changed

+109
-26
lines changed

10 files changed

+109
-26
lines changed

apps/dashboard/src/app/account/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default async function AccountLayout(props: {
1010
children: React.ReactNode;
1111
}) {
1212
return (
13-
<div className="flex min-h-full flex-col bg-background">
13+
<div className="flex min-h-screen flex-col bg-background">
1414
<div className="flex grow flex-col">
1515
<HeaderAndNav />
1616
<div className="border-border border-b py-10">
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"use client";
2+
3+
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
4+
import type { ThirdwebClient } from "thirdweb";
5+
import { upload } from "thirdweb/storage";
6+
import { AccountSettingsPageUI } from "./AccountSettingsPageUI";
7+
8+
export function AccountSettingsPage(props: {
9+
account: Account;
10+
client: ThirdwebClient;
11+
}) {
12+
return (
13+
<AccountSettingsPageUI
14+
account={props.account}
15+
updateAccountImage={async (file) => {
16+
if (file) {
17+
// upload to IPFS
18+
const ipfsUri = await upload({
19+
client: props.client,
20+
files: [file],
21+
});
22+
23+
// TODO - Implement updating the account image with uri
24+
console.log(ipfsUri);
25+
} else {
26+
// TODO - Implement deleting the account image
27+
}
28+
29+
throw new Error("Not implemented");
30+
}}
31+
/>
32+
);
33+
}

apps/dashboard/src/app/account/settings/AccountSettingsPageUI.stories.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ function Variants() {
4141
name: "John Doe",
4242
4343
}}
44+
updateAccountImage={async () => {
45+
await new Promise((resolve) => setTimeout(resolve, 1000));
46+
}}
4447
/>
4548
</div>
4649
<Toaster richColors />

apps/dashboard/src/app/account/settings/AccountSettingsPageUI.tsx

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ type MinimalAccount = Pick<
4242

4343
export function AccountSettingsPageUI(props: {
4444
account: MinimalAccount;
45+
updateAccountImage: (file: File | undefined) => Promise<void>;
4546
}) {
4647
return (
4748
<div className="flex flex-col gap-8">
48-
<AccountAvatarFormControl />
49+
<AccountAvatarFormControl updateAccountImage={props.updateAccountImage} />
4950
<AccountNameFormControl name={props.account.name || ""} />
5051
<AccountEmailFormControl
5152
email={props.account.email || ""}
@@ -58,16 +59,14 @@ export function AccountSettingsPageUI(props: {
5859
);
5960
}
6061

61-
function AccountAvatarFormControl() {
62+
function AccountAvatarFormControl(props: {
63+
updateAccountImage: (file: File | undefined) => Promise<void>;
64+
}) {
6265
const [avatar, setAvatar] = useState<File>(); // TODO: prefill with account avatar
6366

64-
// TODO - implement
6567
const updateAvatarMutation = useMutation({
6668
mutationFn: async (_avatar: File | undefined) => {
67-
// Fake loading
68-
await new Promise((resolve) => setTimeout(resolve, 3000));
69-
console.log("Updating account image to", _avatar);
70-
throw new Error("Not implemented");
69+
await props.updateAccountImage(_avatar);
7170
},
7271
});
7372

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
1+
import { getThirdwebClient } from "@/constants/thirdweb.server";
12
import { redirect } from "next/navigation";
2-
import { AccountSettingsPageUI } from "./AccountSettingsPageUI";
3+
import { getAuthToken } from "../../api/lib/getAuthToken";
4+
import { AccountSettingsPage } from "./AccountSettingsPage";
35
import { getAccount } from "./getAccount";
46

57
export default async function Page() {
68
const account = await getAccount();
9+
const token = getAuthToken();
710

8-
if (!account) {
11+
if (!account || !token) {
912
redirect(`/login?next=${encodeURIComponent("/account")}`);
1013
}
1114

12-
return <AccountSettingsPageUI account={account} />;
15+
return (
16+
<AccountSettingsPage account={account} client={getThirdwebClient(token)} />
17+
);
1318
}

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ import type { Meta, StoryObj } from "@storybook/react";
44
import { mobileViewport } from "../../../../../../../stories/utils";
55
import {
66
DeleteTeamCard,
7-
GeneralSettingsPage,
87
LeaveTeamCard,
9-
} from "./GeneralSettingsPage";
8+
TeamGeneralSettingsPageUI,
9+
} from "./TeamGeneralSettingsPageUI";
1010

1111
const meta = {
1212
title: "Team/Settings/General",
@@ -47,7 +47,12 @@ const testTeam: Team = {
4747
function Story() {
4848
return (
4949
<div className="mx-auto w-full max-w-[1100px] px-4 py-6">
50-
<GeneralSettingsPage team={testTeam} />
50+
<TeamGeneralSettingsPageUI
51+
team={testTeam}
52+
updateTeamImage={async () => {
53+
await new Promise((resolve) => setTimeout(resolve, 1000));
54+
}}
55+
/>
5156
<ComponentVariantions />
5257
<Toaster richColors />
5358
</div>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"use client";
2+
3+
import type { Team } from "@/api/team";
4+
import type { ThirdwebClient } from "thirdweb";
5+
import { upload } from "thirdweb/storage";
6+
import { TeamGeneralSettingsPageUI } from "./TeamGeneralSettingsPageUI";
7+
8+
export function TeamGeneralSettingsPage(props: {
9+
team: Team;
10+
client: ThirdwebClient;
11+
}) {
12+
return (
13+
<TeamGeneralSettingsPageUI
14+
team={props.team}
15+
updateTeamImage={async (file) => {
16+
if (file) {
17+
// upload to IPFS
18+
const uri = await upload({
19+
client: props.client,
20+
files: [file],
21+
});
22+
23+
// TODO - Implement updating the account image with uri
24+
console.log(uri);
25+
} else {
26+
// TODO - Implement deleting the account image
27+
}
28+
29+
throw new Error("Not implemented");
30+
}}
31+
/>
32+
);
33+
}

apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/GeneralSettingsPage.tsx renamed to apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/general/TeamGeneralSettingsPageUI.tsx

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,16 @@ import { FileInput } from "components/shared/FileInput";
1111
import { useState } from "react";
1212
import { toast } from "sonner";
1313

14-
export function GeneralSettingsPage(props: {
14+
export function TeamGeneralSettingsPageUI(props: {
1515
team: Team;
16+
updateTeamImage: (file: File | undefined) => Promise<void>;
1617
}) {
1718
const hasPermissionToDelete = false; // TODO
1819
return (
1920
<div className="flex flex-col gap-8">
2021
<TeamNameFormControl team={props.team} />
2122
<TeamSlugFormControl team={props.team} />
22-
<TeamAvatarFormControl />
23+
<TeamAvatarFormControl updateTeamImage={props.updateTeamImage} />
2324
<TeamIdCard team={props.team} />
2425
<LeaveTeamCard enabled={false} teamName={props.team.name} />
2526
<DeleteTeamCard
@@ -141,16 +142,14 @@ function TeamSlugFormControl(props: {
141142
);
142143
}
143144

144-
function TeamAvatarFormControl() {
145+
function TeamAvatarFormControl(props: {
146+
updateTeamImage: (file: File | undefined) => Promise<void>;
147+
}) {
145148
const [teamAvatar, setTeamAvatar] = useState<File>(); // TODO: prefill with team avatar
146149

147-
// TODO - implement
148150
const updateTeamAvatarMutation = useMutation({
149151
mutationFn: async (_avatar: File | undefined) => {
150-
// Fake loading
151-
await new Promise((resolve) => setTimeout(resolve, 3000));
152-
console.log("Updating team name to", _avatar);
153-
throw new Error("Not implemented");
152+
await props.updateTeamImage(_avatar);
154153
},
155154
});
156155

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
import { getTeamBySlug } from "@/api/team";
2+
import { getThirdwebClient } from "@/constants/thirdweb.server";
23
import { notFound } from "next/navigation";
3-
import { GeneralSettingsPage } from "./general/GeneralSettingsPage";
4+
import { getAuthToken } from "../../../../../api/lib/getAuthToken";
5+
import { TeamGeneralSettingsPage } from "./general/TeamGeneralSettingsPage";
46

57
export default async function Page(props: {
68
params: {
79
team_slug: string;
810
};
911
}) {
1012
const team = await getTeamBySlug(props.params.team_slug);
11-
12-
if (!team) {
13+
const token = getAuthToken();
14+
if (!team || !token) {
1315
notFound();
1416
}
1517

16-
return <GeneralSettingsPage team={team} />;
18+
return (
19+
<TeamGeneralSettingsPage team={team} client={getThirdwebClient(token)} />
20+
);
1721
}

apps/dashboard/src/app/team/[team_slug]/[project_slug]/settings/ProjectGeneralSettingsPageForTeams.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ export function ProjectGeneralSettingsPageForTeams(props: {
1313
const { team_slug, project_slug, apiKey } = props;
1414
const projectSettingsLayout = `/team/${team_slug}/${project_slug}/settings`;
1515

16+
// TODO - add a Project Image form field on this page
17+
1618
return (
1719
<ProjectGeneralSettingsPage
1820
apiKey={apiKey}

0 commit comments

Comments
 (0)