Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions apps/dashboard/src/@/actions/confirmEmail.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use server";

import { getAuthToken } from "../../app/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";

export async function confirmEmailWithOTP(otp: string) {
const token = await getAuthToken();

if (!token) {
return {
errorMessage: "You are not authorized to perform this action",
};
}

const res = await fetch(`${API_SERVER_URL}/v1/account/confirmEmail`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify({
confirmationToken: otp,
}),
});

if (!res.ok) {
const json = await res.json();

if (json.error) {
return {
errorMessage: json.error.message,
};
}

return {
errorMessage: "Failed to confirm email",
};
}
}
12 changes: 9 additions & 3 deletions apps/dashboard/src/@/actions/joinWaitlist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export async function joinTeamWaitlist(options: {
const token = await getAuthToken();

if (!token) {
throw new Error("No Auth token");
return {
errorMessage: "You are not authorized to perform this action",
};
}

const res = await fetch(`${API_SERVER_URL}/v1/teams/${teamSlug}/waitlist`, {
Expand All @@ -27,8 +29,12 @@ export async function joinTeamWaitlist(options: {
});

if (!res.ok) {
throw new Error("Failed to join waitlist");
return {
errorMessage: "Failed to join waitlist",
};
}

return true;
return {
success: true,
};
}
37 changes: 37 additions & 0 deletions apps/dashboard/src/@/actions/updateAccount.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"use server";
import { getAuthToken } from "../../app/api/lib/getAuthToken";
import { API_SERVER_URL } from "../constants/env";

export async function updateAccount(values: {
name?: string;
email?: string;
}) {
const token = await getAuthToken();

if (!token) {
throw new Error("No Auth token");
}

const res = await fetch(`${API_SERVER_URL}/v1/account`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
},
body: JSON.stringify(values),
});

if (!res.ok) {
const json = await res.json();

if (json.error) {
return {
errorMessage: json.error.message,
};
}

return {
errorMessage: "Failed To Update Account",
};
}
}
8 changes: 4 additions & 4 deletions apps/dashboard/src/app/account/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,12 +47,12 @@ async function HeaderAndNav() {
name: "Contracts",
exactMatch: true,
},
{
path: "/account/settings",
name: "Settings",
},
// TODO - enable these links after they are functional
// {
// path: "/account/settings",
// name: "Settings",
// },
// {
// path: "/account/wallets",
// name: "Wallets",
// },
Expand Down
61 changes: 41 additions & 20 deletions apps/dashboard/src/app/account/settings/AccountSettingsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,56 @@
"use client";

import { confirmEmailWithOTP } from "@/actions/confirmEmail";
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: {
account: Account;
client: ThirdwebClient;
}) {
const router = useDashboardRouter();
return (
<div>
<AccountSettingsPageUI
account={props.account}
updateAccountImage={async (file) => {
if (file) {
// upload to IPFS
const ipfsUri = await upload({
client: props.client,
files: [file],
});

// TODO - Implement updating the account image with uri
console.log(ipfsUri);
} else {
// TODO - Implement deleting the account image
}

throw new Error("Not implemented");
}}
/>
<div className="border-border border-b py-10">
<div className="container max-w-[950px]">
<h1 className="font-semibold text-3xl tracking-tight">
Account Settings
</h1>
</div>
</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
account={props.account}
updateEmailWithOTP={async (otp) => {
const res = await confirmEmailWithOTP(otp);
if (res?.errorMessage) {
throw new Error(res.errorMessage);
}
router.refresh();
}}
updateName={async (name) => {
const res = await updateAccount({ name });
if (res?.errorMessage) {
throw new Error(res.errorMessage);
}
router.refresh();
}}
// yes, this is weird -
// to send OTP to email, we use updateAccount
sendEmail={async (email) => {
const res = await updateAccount({ email });
if (res?.errorMessage) {
throw new Error(res.errorMessage);
}
}}
/>
</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Checkbox, CheckboxWithLabel } from "@/components/ui/checkbox";
import type { Meta, StoryObj } from "@storybook/react";
import { useState } from "react";
import { Toaster } from "sonner";
import { ThirdwebProvider } from "thirdweb/react";
import { mobileViewport } from "../../../stories/utils";
import { AccountSettingsPageUI } from "./AccountSettingsPageUI";

Expand Down Expand Up @@ -33,20 +34,63 @@ export const Mobile: Story = {
};

function Variants() {
const [isVerifiedEmail, setIsVerifiedEmail] = useState(true);
const [sendEmailFails, setSendEmailFails] = useState(false);
const [emailConfirmationFails, setEmailConfirmationFails] = useState(false);

return (
<ThirdwebProvider>
<div className="container mx-auto flex w-full max-w-[1132px] flex-col gap-10 py-10">
<AccountSettingsPageUI
account={{
name: "John Doe",
email: "johndoe@gmail.com",
}}
updateAccountImage={async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
/>
<div className="container flex max-w-[1132px] flex-col gap-10 py-10">
<div className="flex flex-col gap-2">
<CheckboxWithLabel>
<Checkbox
checked={isVerifiedEmail}
onCheckedChange={(v) => setIsVerifiedEmail(!!v)}
/>
is Verified Email
</CheckboxWithLabel>

<CheckboxWithLabel>
<Checkbox
checked={sendEmailFails}
onCheckedChange={(v) => setSendEmailFails(!!v)}
/>
Sending Email Fails
</CheckboxWithLabel>

<CheckboxWithLabel>
<Checkbox
checked={emailConfirmationFails}
onCheckedChange={(v) => setEmailConfirmationFails(!!v)}
/>
Email Confirmation Fails
</CheckboxWithLabel>
</div>

<AccountSettingsPageUI
account={{
name: "John Doe",
email: "johndoe@gmail.com",
emailConfirmedAt: isVerifiedEmail
? new Date().toISOString()
: undefined,
}}
updateEmailWithOTP={async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
if (emailConfirmationFails) {
throw new Error("Invalid OTP");
}
}}
updateName={async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
sendEmail={async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
if (sendEmailFails) {
throw new Error("Email already exists");
}
}}
/>
<Toaster richColors />
</ThirdwebProvider>
</div>
);
}
Loading
Loading