Skip to content

Commit

Permalink
[TOOL-3318] Dashboard: Add Upload Account Avatar feature in account s…
Browse files Browse the repository at this point in the history
…ettings page (#6179)
  • Loading branch information
MananTank committed Feb 5, 2025
1 parent 769a927 commit 0fa8354
Show file tree
Hide file tree
Showing 13 changed files with 73 additions and 37 deletions.
1 change: 1 addition & 0 deletions apps/dashboard/src/@/actions/updateAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { API_SERVER_URL } from "../constants/env";
export async function updateAccount(values: {
name?: string;
email?: string;
image?: string | null;
}) {
const token = await getAuthToken();

Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/src/@3rdweb-sdk/react/hooks/useApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export type Account = {
id: string;
isStaff: boolean;
creatorWalletAddress: string;
image?: string | null;
name?: string;
email?: string;
advancedEnabled: boolean;
Expand Down
8 changes: 3 additions & 5 deletions apps/dashboard/src/app/account/components/AccountHeaderUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export type AccountHeaderCompProps = {
connectButton: React.ReactNode;
teamsAndProjects: Array<{ team: Team; projects: Project[] }>;
createProject: (team: Team) => void;
account: Pick<Account, "email" | "id"> | undefined;
account: Pick<Account, "email" | "id" | "image"> | undefined;
client: ThirdwebClient;
};

Expand All @@ -41,10 +41,9 @@ export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) {
href="/account"
className="flex flex-row items-center gap-2 font-normal text-sm"
>
{/* TODO - set account Image */}
<GradientAvatar
id={props.account?.id || "default"}
src={""}
src={props.account?.image || ""}
className="size-6"
client={props.client}
/>
Expand Down Expand Up @@ -90,10 +89,9 @@ export function AccountHeaderMobileUI(props: AccountHeaderCompProps) {
"flex flex-row items-center gap-2 font-normal text-foreground text-sm",
)}
>
{/* TODO - set account image */}
<GradientAvatar
id={props.account?.id}
src={props.account ? "" : undefined}
src={props.account?.image || ""}
className="size-6"
client={props.client}
/>
Expand Down
21 changes: 19 additions & 2 deletions apps/dashboard/src/app/account/settings/AccountSettingsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { updateAccount } from "@/actions/updateAccount";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import type { ThirdwebClient } from "thirdweb";
import { upload } from "thirdweb/storage";
import { AccountSettingsPageUI } from "./AccountSettingsPageUI";

export function AccountSettingsPage(props: {
Expand All @@ -23,9 +24,25 @@ export function AccountSettingsPage(props: {
</div>
<div className="container max-w-[950px] grow pt-8 pb-20">
<AccountSettingsPageUI
// TODO - remove hide props these when these fields are functional
hideAvatar
hideDeleteAccount
client={props.client}
updateAccountAvatar={async (file) => {
let uri: string | undefined = undefined;

if (file) {
// upload to IPFS
uri = await upload({
client: props.client,
files: [file],
});
}

await updateAccount({
image: uri,
});

router.refresh();
}}
account={props.account}
updateEmailWithOTP={async (otp) => {
const res = await confirmEmailWithOTP(otp);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { Toaster } from "sonner";
Expand Down Expand Up @@ -33,6 +34,8 @@ export const Mobile: Story = {
},
};

const client = getThirdwebClient();

function Variants() {
const [isVerifiedEmail, setIsVerifiedEmail] = useState(true);
const [sendEmailFails, setSendEmailFails] = useState(false);
Expand Down Expand Up @@ -74,6 +77,10 @@ function Variants() {
? new Date().toISOString()
: undefined,
}}
client={client}
updateAccountAvatar={async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
updateEmailWithOTP={async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
if (emailConfirmationFails) {
Expand Down
33 changes: 23 additions & 10 deletions apps/dashboard/src/app/account/settings/AccountSettingsPageUI.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
InputOTPSlot,
} from "@/components/ui/input-otp";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import { resolveSchemeWithErrorHandler } from "@/lib/resolveSchemeWithErrorHandler";
import { cn } from "@/lib/utils";
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
import { zodResolver } from "@hookform/resolvers/zod";
Expand All @@ -39,30 +40,35 @@ import { EllipsisIcon } from "lucide-react";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import type { ThirdwebClient } from "thirdweb";
import { z } from "zod";
import { FileInput } from "../../../components/shared/FileInput";

type MinimalAccount = Pick<
Account,
"name" | "email" | "emailConfirmedAt" | "unconfirmedEmail"
"name" | "email" | "emailConfirmedAt" | "unconfirmedEmail" | "image"
>;

export function AccountSettingsPageUI(props: {
account: MinimalAccount;
// TODO - remove hide props these when these fields are functional
hideAvatar?: boolean;
hideDeleteAccount?: boolean;
sendEmail: (email: string) => Promise<void>;
updateName: (name: string) => Promise<void>;
updateEmailWithOTP: (otp: string) => Promise<void>;
updateAccountAvatar: (avatar: File | undefined) => Promise<void>;
client: ThirdwebClient;
}) {
return (
<div className="flex flex-col gap-8">
{!props.hideAvatar && <AccountAvatarFormControl />}
<AccountNameFormControl
name={props.account.name || ""}
updateName={(name) => props.updateName(name)}
/>
<AccountAvatarFormControl
updateAccountAvatar={props.updateAccountAvatar}
avatar={props.account.image || undefined}
client={props.client}
/>
<AccountEmailFormControl
email={props.account.email || ""}
status={props.account.emailConfirmedAt ? "verified" : "unverified"}
Expand All @@ -75,18 +81,24 @@ export function AccountSettingsPageUI(props: {
);
}

function AccountAvatarFormControl() {
function AccountAvatarFormControl(props: {
updateAccountAvatar: (avatar: File | undefined) => Promise<void>;
avatar: string | undefined;
client: ThirdwebClient;
}) {
const accountAvatarUrl = resolveSchemeWithErrorHandler({
client: props.client,
uri: props.avatar,
});
const [avatar, setAvatar] = useState<File>();

// TODO - implement
const updateAvatarMutation = useMutation({
mutationFn: async () => {
await new Promise((resolve) => setTimeout(resolve, 3000));
mutationFn: (_avatar: File | undefined) => {
return props.updateAccountAvatar(_avatar);
},
});

function handleSave() {
const promises = updateAvatarMutation.mutateAsync();
const promises = updateAvatarMutation.mutateAsync(avatar);
toast.promise(promises, {
success: "Account avatar updated successfully",
error: "Failed to update account avatar",
Expand Down Expand Up @@ -118,6 +130,7 @@ function AccountAvatarFormControl() {
setValue={setAvatar}
className="w-20 rounded-full lg:w-28"
disableHelperText
fileUrl={accountAvatarUrl}
/>
</div>
</SettingsCard>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import type { ThirdwebClient } from "thirdweb";
export function AccountButton(props: {
logout: () => void;
connectButton: React.ReactNode;
account?: Pick<Account, "email" | "id">;
account?: Pick<Account, "email" | "id" | "image">;
client: ThirdwebClient;
}) {
const { setTheme, theme } = useTheme();
Expand All @@ -35,11 +35,10 @@ export function AccountButton(props: {
>
{/* Don't remove the div */}
<div>
{/* TODO - set account image */}
<GradientAvatar
id={props.account?.id || "default"}
src={""}
className="size-9"
src={props.account?.image || ""}
className="size-9 border"
client={props.client}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export function NebulaAccountButton(props: {
<div className="shrink-0">
<GradientAvatar
id={props.account?.id || "default"}
src={""}
src={props.account?.image || ""}
className="size-9 rounded-lg"
client={client}
/>
Expand All @@ -88,7 +88,7 @@ export function NebulaAccountButton(props: {
<div className="shrink-0">
<GradientAvatar
id={props.account?.id || "default"}
src={""}
src={props.account?.image || ""}
className="size-9 rounded-full"
client={client}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getTeamSettingsLinks } from "./getTeamSettingsLinks";

export function TeamSettingsSidebar(props: {
team: Team;
account: Pick<Account, "id"> | undefined;
account: Pick<Account, "id" | "image"> | undefined;
client: ThirdwebClient;
}) {
const teamLinks = getTeamSettingsLinks(props.team.slug);
Expand All @@ -34,7 +34,7 @@ export function TeamSettingsSidebar(props: {
team={props.team}
titleAvatarIcon={{
id: props.account?.id,
src: "", // TODO - set account image
src: props.account?.image || "",
}}
client={props.client}
/>
Expand All @@ -60,7 +60,7 @@ function RenderLinkGroup(props: {
<div className="flex items-center gap-1.5 px-4">
<GradientAvatar
src={props.titleAvatarIcon.src}
className="size-4"
className="size-[18px]"
id={props.titleAvatarIcon.id}
client={props.client}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,22 @@
"use client";

import type { Team } from "@/api/team";
import { useThirdwebClient } from "@/constants/thirdweb.client";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import type { ThirdwebClient } from "thirdweb";
import { upload } from "thirdweb/storage";
import { TeamGeneralSettingsPageUI } from "./TeamGeneralSettingsPageUI";
import { updateTeam } from "./updateTeam";

export function TeamGeneralSettingsPage(props: {
team: Team;
authToken: string;
client: ThirdwebClient;
}) {
const router = useDashboardRouter();
const client = useThirdwebClient();

return (
<TeamGeneralSettingsPageUI
team={props.team}
client={client}
client={props.client}
updateTeamField={async (teamValue) => {
await updateTeam({
teamId: props.team.id,
Expand All @@ -38,7 +36,7 @@ export function TeamGeneralSettingsPage(props: {
if (file) {
// upload to IPFS
uri = await upload({
client: getThirdwebClient(props.authToken),
client: props.client,
files: [file],
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ function TeamAvatarFormControl(props: {
disabled: false,
isPending: updateTeamAvatarMutation.isPending,
}}
noPermissionText={undefined} // TODO
noPermissionText={undefined}
errorText={undefined}
>
<div className="flex flex-row gap-4 md:justify-between">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getTeamBySlug } from "@/api/team";
import { getThirdwebClient } from "@/constants/thirdweb.server";
import { notFound } from "next/navigation";
import { getAuthToken } from "../../../../../api/lib/getAuthToken";
import { TeamGeneralSettingsPage } from "./general/TeamGeneralSettingsPage";
Expand All @@ -14,5 +15,7 @@ export default async function Page(props: {
notFound();
}

return <TeamGeneralSettingsPage team={team} authToken={token} />;
return (
<TeamGeneralSettingsPage team={team} client={getThirdwebClient(token)} />
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export function TeamSelectionUI(props: {
currentTeam: Team | undefined;
teamsAndProjects: Array<{ team: Team; projects: Project[] }>;
upgradeTeamLink: string | undefined;
account: Pick<Account, "email" | "id"> | undefined;
account: Pick<Account, "email" | "id" | "image"> | undefined;
client: ThirdwebClient;
}) {
const { setHoveredTeam, currentTeam, teamsAndProjects } = props;
Expand Down Expand Up @@ -52,9 +52,8 @@ export function TeamSelectionUI(props: {
asChild
>
<Link href="/account">
{/* TODO set Image src */}
<GradientAvatar
src={""}
src={props.account?.image || ""}
className="size-4"
id={props.account?.id}
client={props.client}
Expand Down

0 comments on commit 0fa8354

Please sign in to comment.