Skip to content

Commit 0fa8354

Browse files
committed
[TOOL-3318] Dashboard: Add Upload Account Avatar feature in account settings page (#6179)
1 parent 769a927 commit 0fa8354

File tree

13 files changed

+73
-37
lines changed

13 files changed

+73
-37
lines changed

apps/dashboard/src/@/actions/updateAccount.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { API_SERVER_URL } from "../constants/env";
55
export async function updateAccount(values: {
66
name?: string;
77
email?: string;
8+
image?: string | null;
89
}) {
910
const token = await getAuthToken();
1011

apps/dashboard/src/@3rdweb-sdk/react/hooks/useApi.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export type Account = {
3939
id: string;
4040
isStaff: boolean;
4141
creatorWalletAddress: string;
42+
image?: string | null;
4243
name?: string;
4344
email?: string;
4445
advancedEnabled: boolean;

apps/dashboard/src/app/account/components/AccountHeaderUI.tsx

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export type AccountHeaderCompProps = {
1717
connectButton: React.ReactNode;
1818
teamsAndProjects: Array<{ team: Team; projects: Project[] }>;
1919
createProject: (team: Team) => void;
20-
account: Pick<Account, "email" | "id"> | undefined;
20+
account: Pick<Account, "email" | "id" | "image"> | undefined;
2121
client: ThirdwebClient;
2222
};
2323

@@ -41,10 +41,9 @@ export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) {
4141
href="/account"
4242
className="flex flex-row items-center gap-2 font-normal text-sm"
4343
>
44-
{/* TODO - set account Image */}
4544
<GradientAvatar
4645
id={props.account?.id || "default"}
47-
src={""}
46+
src={props.account?.image || ""}
4847
className="size-6"
4948
client={props.client}
5049
/>
@@ -90,10 +89,9 @@ export function AccountHeaderMobileUI(props: AccountHeaderCompProps) {
9089
"flex flex-row items-center gap-2 font-normal text-foreground text-sm",
9190
)}
9291
>
93-
{/* TODO - set account image */}
9492
<GradientAvatar
9593
id={props.account?.id}
96-
src={props.account ? "" : undefined}
94+
src={props.account?.image || ""}
9795
className="size-6"
9896
client={props.client}
9997
/>

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

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { updateAccount } from "@/actions/updateAccount";
55
import { useDashboardRouter } from "@/lib/DashboardRouter";
66
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
77
import type { ThirdwebClient } from "thirdweb";
8+
import { upload } from "thirdweb/storage";
89
import { AccountSettingsPageUI } from "./AccountSettingsPageUI";
910

1011
export function AccountSettingsPage(props: {
@@ -23,9 +24,25 @@ export function AccountSettingsPage(props: {
2324
</div>
2425
<div className="container max-w-[950px] grow pt-8 pb-20">
2526
<AccountSettingsPageUI
26-
// TODO - remove hide props these when these fields are functional
27-
hideAvatar
2827
hideDeleteAccount
28+
client={props.client}
29+
updateAccountAvatar={async (file) => {
30+
let uri: string | undefined = undefined;
31+
32+
if (file) {
33+
// upload to IPFS
34+
uri = await upload({
35+
client: props.client,
36+
files: [file],
37+
});
38+
}
39+
40+
await updateAccount({
41+
image: uri,
42+
});
43+
44+
router.refresh();
45+
}}
2946
account={props.account}
3047
updateEmailWithOTP={async (otp) => {
3148
const res = await confirmEmailWithOTP(otp);

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox";
2+
import { getThirdwebClient } from "@/constants/thirdweb.server";
23
import type { Meta, StoryObj } from "@storybook/react";
34
import { useState } from "react";
45
import { Toaster } from "sonner";
@@ -33,6 +34,8 @@ export const Mobile: Story = {
3334
},
3435
};
3536

37+
const client = getThirdwebClient();
38+
3639
function Variants() {
3740
const [isVerifiedEmail, setIsVerifiedEmail] = useState(true);
3841
const [sendEmailFails, setSendEmailFails] = useState(false);
@@ -74,6 +77,10 @@ function Variants() {
7477
? new Date().toISOString()
7578
: undefined,
7679
}}
80+
client={client}
81+
updateAccountAvatar={async () => {
82+
await new Promise((resolve) => setTimeout(resolve, 1000));
83+
}}
7784
updateEmailWithOTP={async () => {
7885
await new Promise((resolve) => setTimeout(resolve, 1000));
7986
if (emailConfirmationFails) {

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

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
InputOTPSlot,
3131
} from "@/components/ui/input-otp";
3232
import { useDashboardRouter } from "@/lib/DashboardRouter";
33+
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
3334
import { cn } from "@/lib/utils";
3435
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
3536
import { zodResolver } from "@hookform/resolvers/zod";
@@ -39,30 +40,35 @@ import { EllipsisIcon } from "lucide-react";
3940
import { useState } from "react";
4041
import { useForm } from "react-hook-form";
4142
import { toast } from "sonner";
43+
import type { ThirdwebClient } from "thirdweb";
4244
import { z } from "zod";
4345
import { FileInput } from "../../../components/shared/FileInput";
4446

4547
type MinimalAccount = Pick<
4648
Account,
47-
"name" | "email" | "emailConfirmedAt" | "unconfirmedEmail"
49+
"name" | "email" | "emailConfirmedAt" | "unconfirmedEmail" | "image"
4850
>;
4951

5052
export function AccountSettingsPageUI(props: {
5153
account: MinimalAccount;
52-
// TODO - remove hide props these when these fields are functional
53-
hideAvatar?: boolean;
5454
hideDeleteAccount?: boolean;
5555
sendEmail: (email: string) => Promise<void>;
5656
updateName: (name: string) => Promise<void>;
5757
updateEmailWithOTP: (otp: string) => Promise<void>;
58+
updateAccountAvatar: (avatar: File | undefined) => Promise<void>;
59+
client: ThirdwebClient;
5860
}) {
5961
return (
6062
<div className="flex flex-col gap-8">
61-
{!props.hideAvatar && <AccountAvatarFormControl />}
6263
<AccountNameFormControl
6364
name={props.account.name || ""}
6465
updateName={(name) => props.updateName(name)}
6566
/>
67+
<AccountAvatarFormControl
68+
updateAccountAvatar={props.updateAccountAvatar}
69+
avatar={props.account.image || undefined}
70+
client={props.client}
71+
/>
6672
<AccountEmailFormControl
6773
email={props.account.email || ""}
6874
status={props.account.emailConfirmedAt ? "verified" : "unverified"}
@@ -75,18 +81,24 @@ export function AccountSettingsPageUI(props: {
7581
);
7682
}
7783

78-
function AccountAvatarFormControl() {
84+
function AccountAvatarFormControl(props: {
85+
updateAccountAvatar: (avatar: File | undefined) => Promise<void>;
86+
avatar: string | undefined;
87+
client: ThirdwebClient;
88+
}) {
89+
const accountAvatarUrl = resolveSchemeWithErrorHandler({
90+
client: props.client,
91+
uri: props.avatar,
92+
});
7993
const [avatar, setAvatar] = useState<File>();
80-
81-
// TODO - implement
8294
const updateAvatarMutation = useMutation({
83-
mutationFn: async () => {
84-
await new Promise((resolve) => setTimeout(resolve, 3000));
95+
mutationFn: (_avatar: File | undefined) => {
96+
return props.updateAccountAvatar(_avatar);
8597
},
8698
});
8799

88100
function handleSave() {
89-
const promises = updateAvatarMutation.mutateAsync();
101+
const promises = updateAvatarMutation.mutateAsync(avatar);
90102
toast.promise(promises, {
91103
success: "Account avatar updated successfully",
92104
error: "Failed to update account avatar",
@@ -118,6 +130,7 @@ function AccountAvatarFormControl() {
118130
setValue={setAvatar}
119131
className="w-20 rounded-full lg:w-28"
120132
disableHelperText
133+
fileUrl={accountAvatarUrl}
121134
/>
122135
</div>
123136
</SettingsCard>

apps/dashboard/src/app/components/Header/SecondaryNav/account-button.client.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type { ThirdwebClient } from "thirdweb";
1818
export function AccountButton(props: {
1919
logout: () => void;
2020
connectButton: React.ReactNode;
21-
account?: Pick<Account, "email" | "id">;
21+
account?: Pick<Account, "email" | "id" | "image">;
2222
client: ThirdwebClient;
2323
}) {
2424
const { setTheme, theme } = useTheme();
@@ -35,11 +35,10 @@ export function AccountButton(props: {
3535
>
3636
{/* Don't remove the div */}
3737
<div>
38-
{/* TODO - set account image */}
3938
<GradientAvatar
4039
id={props.account?.id || "default"}
41-
src={""}
42-
className="size-9"
40+
src={props.account?.image || ""}
41+
className="size-9 border"
4342
client={props.client}
4443
/>
4544
</div>

apps/dashboard/src/app/nebula-app/(app)/components/NebulaAccountButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export function NebulaAccountButton(props: {
6161
<div className="shrink-0">
6262
<GradientAvatar
6363
id={props.account?.id || "default"}
64-
src={""}
64+
src={props.account?.image || ""}
6565
className="size-9 rounded-lg"
6666
client={client}
6767
/>
@@ -88,7 +88,7 @@ export function NebulaAccountButton(props: {
8888
<div className="shrink-0">
8989
<GradientAvatar
9090
id={props.account?.id || "default"}
91-
src={""}
91+
src={props.account?.image || ""}
9292
className="size-9 rounded-full"
9393
client={client}
9494
/>

apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/_components/sidebar/TeamSettingsSidebar.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import { getTeamSettingsLinks } from "./getTeamSettingsLinks";
77

88
export function TeamSettingsSidebar(props: {
99
team: Team;
10-
account: Pick<Account, "id"> | undefined;
10+
account: Pick<Account, "id" | "image"> | undefined;
1111
client: ThirdwebClient;
1212
}) {
1313
const teamLinks = getTeamSettingsLinks(props.team.slug);
@@ -34,7 +34,7 @@ export function TeamSettingsSidebar(props: {
3434
team={props.team}
3535
titleAvatarIcon={{
3636
id: props.account?.id,
37-
src: "", // TODO - set account image
37+
src: props.account?.image || "",
3838
}}
3939
client={props.client}
4040
/>
@@ -60,7 +60,7 @@ function RenderLinkGroup(props: {
6060
<div className="flex items-center gap-1.5 px-4">
6161
<GradientAvatar
6262
src={props.titleAvatarIcon.src}
63-
className="size-4"
63+
className="size-[18px]"
6464
id={props.titleAvatarIcon.id}
6565
client={props.client}
6666
/>

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,22 @@
11
"use client";
22

33
import type { Team } from "@/api/team";
4-
import { useThirdwebClient } from "@/constants/thirdweb.client";
5-
import { getThirdwebClient } from "@/constants/thirdweb.server";
64
import { useDashboardRouter } from "@/lib/DashboardRouter";
5+
import type { ThirdwebClient } from "thirdweb";
76
import { upload } from "thirdweb/storage";
87
import { TeamGeneralSettingsPageUI } from "./TeamGeneralSettingsPageUI";
98
import { updateTeam } from "./updateTeam";
109

1110
export function TeamGeneralSettingsPage(props: {
1211
team: Team;
13-
authToken: string;
12+
client: ThirdwebClient;
1413
}) {
1514
const router = useDashboardRouter();
16-
const client = useThirdwebClient();
1715

1816
return (
1917
<TeamGeneralSettingsPageUI
2018
team={props.team}
21-
client={client}
19+
client={props.client}
2220
updateTeamField={async (teamValue) => {
2321
await updateTeam({
2422
teamId: props.team.id,
@@ -38,7 +36,7 @@ export function TeamGeneralSettingsPage(props: {
3836
if (file) {
3937
// upload to IPFS
4038
uri = await upload({
41-
client: getThirdwebClient(props.authToken),
39+
client: props.client,
4240
files: [file],
4341
});
4442
}

0 commit comments

Comments
 (0)