Skip to content

Commit

Permalink
[TOOL-3446] Dashboard: Revamp account onboarding, Add Team onboarding
Browse files Browse the repository at this point in the history
  • Loading branch information
MananTank committed Feb 26, 2025
1 parent 3f6d525 commit 865d3d4
Show file tree
Hide file tree
Showing 46 changed files with 2,277 additions and 1,067 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilly-trams-wash.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@thirdweb-dev/service-utils": patch
---

Update `TeamResponse` type
35 changes: 32 additions & 3 deletions apps/dashboard/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Inter as interFont } from "next/font/google";
// biome-ignore lint/style/useImportType: <explanation>
import React from "react";
import { useEffect } from "react";
import { Toaster } from "sonner";
import { Button } from "../src/@/components/ui/button";

const queryClient = new QueryClient();
Expand All @@ -16,8 +17,30 @@ const fontSans = interFont({
variable: "--font-sans",
});

const customViewports = {
xs: {
// Regular sized phones (iphone 15 / 15 pro)
name: "iPhone",
styles: {
width: "390px",
height: "844px",
},
},
sm: {
// Larger phones (iphone 15 plus / 15 pro max)
name: "iPhone Plus",
styles: {
width: "430px",
height: "932px",
},
},
};

const preview: Preview = {
parameters: {
viewport: {
viewports: customViewports,
},
controls: {
matchers: {
color: /(background|color)$/i,
Expand Down Expand Up @@ -57,13 +80,13 @@ function StoryLayout(props: {

return (
<QueryClientProvider client={queryClient}>
<div className="flex min-h-screen min-w-0 flex-col bg-background text-foreground">
<div className="flex min-h-dvh min-w-0 flex-col bg-background text-foreground">
<div className="flex justify-end gap-2 border-b p-4">
<Button
onClick={() => setTheme("dark")}
size="sm"
variant={theme === "dark" ? "default" : "outline"}
className="h-auto w-auto rounded-full p-2"
className="h-auto w-auto shrink-0 rounded-full p-2"
>
<MoonIcon className="size-4" />
</Button>
Expand All @@ -72,14 +95,20 @@ function StoryLayout(props: {
onClick={() => setTheme("light")}
size="sm"
variant={theme === "light" ? "default" : "outline"}
className="h-auto w-auto rounded-full p-2"
className="h-auto w-auto shrink-0 rounded-full p-2"
>
<SunIcon className="size-4" />
</Button>
</div>

<div className="flex min-w-0 grow flex-col">{props.children}</div>
<ToasterSetup />
</div>
</QueryClientProvider>
);
}

function ToasterSetup() {
const { theme } = useTheme();
return <Toaster richColors theme={theme === "light" ? "light" : "dark"} />;
}
22 changes: 8 additions & 14 deletions apps/dashboard/src/@/components/blocks/pricing-card.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"use client";
import type { Team } from "@/api/team";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
Expand All @@ -6,10 +7,12 @@ import { TrackedLinkTW } from "@/components/ui/tracked-link";
import { cn } from "@/lib/utils";
import { CheckIcon, CircleAlertIcon, CircleDollarSignIcon } from "lucide-react";
import type React from "react";
import { useState } from "react";
import { TEAM_PLANS } from "utils/pricing";
import { remainingDays } from "../../../utils/date-utils";
import type { RedirectBillingCheckoutAction } from "../../actions/billing";
import { CheckoutButton } from "../billing";
import { Spinner } from "../ui/Spinner/Spinner";

type ButtonProps = React.ComponentProps<typeof Button>;

Expand All @@ -31,7 +34,6 @@ type PricingCardProps = {
ctaHint?: string;
highlighted?: boolean;
current?: boolean;
canTrialGrowth?: boolean;
activeTrialEndsAt?: string;
redirectPath: string;
redirectToCheckout: RedirectBillingCheckoutAction;
Expand All @@ -43,11 +45,11 @@ export const PricingCard: React.FC<PricingCardProps> = ({
cta,
highlighted = false,
current = false,
canTrialGrowth = false,
activeTrialEndsAt,
redirectPath,
redirectToCheckout,
}) => {
const [isRouteLoading, setIsRouteLoading] = useState(false);
const plan = TEAM_PLANS[billingPlan];
const isCustomPrice = typeof plan.price === "string";

Expand Down Expand Up @@ -88,18 +90,7 @@ export const PricingCard: React.FC<PricingCardProps> = ({
<div className="flex flex-col gap-0.5">
<div className="flex items-center gap-2">
<span className="font-semibold text-3xl text-foreground tracking-tight">
{isCustomPrice ? (
plan.price
) : canTrialGrowth ? (
<>
<span className="text-muted-foreground line-through">
${plan.price}
</span>{" "}
$0
</>
) : (
`$${plan.price}`
)}
${plan.price}
</span>

{!isCustomPrice && (
Expand Down Expand Up @@ -154,7 +145,10 @@ export const PricingCard: React.FC<PricingCardProps> = ({
sku={billingPlan === "starter" ? "plan:starter" : "plan:growth"}
redirectPath={redirectPath}
redirectToCheckout={redirectToCheckout}
className="gap-2"
onClick={() => setIsRouteLoading(true)}
>
{isRouteLoading && <Spinner className="size-4" />}
{cta.title}
</CheckoutButton>
) : (
Expand Down
173 changes: 72 additions & 101 deletions apps/dashboard/src/@3rdweb-sdk/react/hooks/useApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,6 @@ export type Account = {
// TODO - add image URL
};

interface UpdateAccountInput {
name?: string;
email?: string;
linkWallet?: boolean;
subscribeToUpdates?: boolean;
onboardSkipped?: boolean;
}

interface UpdateAccountNotificationsInput {
billing: "email" | "none";
updates: "email" | "none";
Expand Down Expand Up @@ -140,45 +132,40 @@ export function useAccountCredits() {
});
}

export function useUpdateAccount() {
const queryClient = useQueryClient();
const address = useActiveAccount()?.address;

return useMutation({
mutationFn: async (input: UpdateAccountInput) => {
type Result = {
data: object;
error?: { message: string };
};
type UpdateAccountParams = {
name?: string;
email?: string;
linkWallet?: boolean;
subscribeToUpdates?: boolean;
onboardSkipped?: boolean;
};

const res = await apiServerProxy<Result>({
pathname: "/v1/account",
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
export async function updateAccountClient(input: UpdateAccountParams) {
type Result = {
data: object;
error?: { message: string };
};

if (!res.ok) {
throw new Error(res.error);
}
const res = await apiServerProxy<Result>({
pathname: "/v1/account",
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});

const json = res.data;
if (!res.ok) {
throw new Error(res.error);
}

if (json.error) {
throw new Error(json.error.message);
}
const json = res.data;

return json.data;
},
if (json.error) {
throw new Error(json.error.message);
}

onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: accountKeys.me(address || ""),
});
},
});
return json.data;
}

export function useUpdateNotifications() {
Expand Down Expand Up @@ -221,77 +208,61 @@ export function useUpdateNotifications() {
});
}

export function useConfirmEmail() {
return useMutation({
mutationFn: async (input: ConfirmEmailInput) => {
type Result = {
error?: { message: string };
data: { team: Team; account: Account };
};

const res = await apiServerProxy<Result>({
pathname: "/v1/account/confirmEmail",
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});

if (!res.ok) {
throw new Error(res.error);
}

const json = res.data;

if (json.error) {
throw new Error(json.error.message);
}
export const verifyEmailClient = async (input: ConfirmEmailInput) => {
type Result = {
error?: { message: string };
data: { team: Team; account: Account };
};

return json.data;
const res = await apiServerProxy<Result>({
pathname: "/v1/account/confirmEmail",
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(input),
});
}

export function useResendEmailConfirmation() {
const address = useActiveAccount()?.address;
const queryClient = useQueryClient();
if (!res.ok) {
throw new Error(res.error);
}

return useMutation({
mutationFn: async () => {
type Result = {
error?: { message: string };
data: object;
};
const json = res.data;

const res = await apiServerProxy<Result>({
pathname: "/v1/account/resendEmailConfirmation",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({}),
});

if (!res.ok) {
throw new Error(res.error);
}
if (json.error) {
throw new Error(json.error.message);
}

const json = res.data;
return json.data;
};

if (json.error) {
throw new Error(json.error.message);
}
export const resendEmailClient = async () => {
type Result = {
error?: { message: string };
data: object;
};

return json.data;
},
onSuccess: () => {
return queryClient.invalidateQueries({
queryKey: accountKeys.me(address || ""),
});
const res = await apiServerProxy<Result>({
pathname: "/v1/account/resendEmailConfirmation",
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({}),
});
}

if (!res.ok) {
throw new Error(res.error);
}

const json = res.data;

if (json.error) {
throw new Error(json.error.message);
}

return json.data;
};

export async function createProjectClient(
teamId: string,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ export async function RouteListCard({
<CardHeader className="flex flex-row items-center justify-between p-4">
<div className="flex flex-row items-center gap-2">
{resolvedOriginTokenIconUri ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={resolvedOriginTokenIconUri}
alt={originTokenAddress}
Expand All @@ -83,6 +84,7 @@ export async function RouteListCard({
<div className="size-8 rounded-full bg-white/10" />
)}
{resolvedDestinationTokenIconUri ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={resolvedDestinationTokenIconUri}
alt={destinationTokenAddress}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export async function RouteListRow({
<div className="flex items-center gap-1">
{resolvedOriginTokenIconUri ? (
// For now we're using a normal img tag because the domain for these images is unknown
// eslint-disable-next-line @next/next/no-img-element
<img
src={resolvedOriginTokenIconUri}
alt={originTokenAddress}
Expand Down Expand Up @@ -110,6 +111,7 @@ export async function RouteListRow({
<div className="flex flex-row items-center gap-4">
<div className="flex items-center gap-1">
{resolvedDestinationTokenIconUri ? (
// eslint-disable-next-line @next/next/no-img-element
<img
src={resolvedDestinationTokenIconUri}
alt={destinationTokenAddress}
Expand Down
Loading

0 comments on commit 865d3d4

Please sign in to comment.