Replace legacy dashboard with Teams and Projects dashboard (#5327)
## Changes Summary ### Huge changes * Delete all `/dashboard/**` pages and make the `/team` the default * Rename all the links `/dashboard/**` to the new equivalent + Setup redirects ### New pages Added in team and account layouts * `/account/contracts` * `/team/<team-slug>/~/contracts` * `/account/devices` * new team overview page * `/team/<team-slug>/~/usage/storage` ### Pages Moved * Engine pages moved from project to team * Ecosystem pages moved from project to team * Team's projects page moved to /projects ( `/team/<team-slug>/~/projects` ) ### Links to these pages removed for now (page not deleted) in new dashboard because they are not yet ready * `/account/wallets`, `/account/settings` ### Minor changes * Add the `AutoConnect` component directly where we render `ThirdwebProvider` so it is always triggered and not only when certain components are rendered, and remove AutoConnect at component level * Move `/chain/validate` to app router inside (dashboard) layout * Remove unnecessary breadcrumb on /explore base page * Various UI adjustments / improvements * Add billing alerts on top of all team, project, account pages ### Fixes * Fix theme switcher not working sometimes in app router * Engine pages crashing in team dashboard * Revert color changes done in a recent a PR, fix colors in project header, app footer done. * Fix Team avatar not resolving IPFS * Fix various react warnings * Fix missing Overview tab on the ecosystem page ### Improvements * Reduced redirects on in-app-wallets and connect analytics page by making the landing page the default. For example: render analytics on `/connect/in-app-wallets` instead of landing on `/connect/in-app-wallets` and then redirecting to `/connect/in-app-wallets/analytics` and then rendering the page )
This commit is contained in:
+69
-124
@@ -1,5 +1,69 @@
|
||||
const legacyDashboardToTeamRedirects = [
|
||||
{
|
||||
source: "/dashboard",
|
||||
destination: "/team",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/contracts/:path*",
|
||||
destination: "/team/~/~/contracts",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/connect/ecosystem/:path*",
|
||||
destination: "/team/~/~/ecosystem/:path*",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/engine/:path*",
|
||||
destination: "/team/~/~/engine/:path*",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/settings/api-keys",
|
||||
destination: "/team/~/~/projects",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/settings/devices",
|
||||
destination: "/account/devices",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/settings/billing",
|
||||
destination: "/team/~/~/settings/billing",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/settings/gas-credits",
|
||||
destination: "/team/~/~/settings/credits",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/settings/usage",
|
||||
destination: "/team/~/~/usage",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/settings/storage",
|
||||
destination: "/team/~/~/usage/storage",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/settings/notifications",
|
||||
destination: "/team/~/~/settings/notifications",
|
||||
permanent: false,
|
||||
},
|
||||
// rest of the /dashboard/* routes
|
||||
{
|
||||
source: "/dashboard/:path*",
|
||||
destination: "/team",
|
||||
permanent: false,
|
||||
},
|
||||
];
|
||||
|
||||
/** @type {import('next').NextConfig['redirects']} */
|
||||
function redirects() {
|
||||
async function redirects() {
|
||||
return [
|
||||
{
|
||||
source: "/portal/:match*",
|
||||
@@ -11,16 +75,6 @@ function redirects() {
|
||||
destination: "/solutions/chains",
|
||||
permanent: true,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/publish/:path*",
|
||||
destination: "/contracts/publish/:path*",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/mumbai/publish/:path*",
|
||||
destination: "/contracts/publish/:path*",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/contracts/release",
|
||||
destination: "/contracts/publish",
|
||||
@@ -97,23 +151,8 @@ function redirects() {
|
||||
destination: "/polygon-zkevm",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/settings",
|
||||
destination: "/dashboard/settings/api-keys",
|
||||
permanent: false,
|
||||
},
|
||||
// backwards compat: page moved to pages/settings/api-key
|
||||
{
|
||||
source: "/dashboard/api-keys",
|
||||
destination: "/dashboard/settings/api-keys",
|
||||
permanent: false,
|
||||
},
|
||||
// backwards compat: page moved to pages/settings/devices
|
||||
{
|
||||
source: "/dashboard/settings/account",
|
||||
destination: "/dashboard/settings/devices",
|
||||
permanent: false,
|
||||
},
|
||||
|
||||
{
|
||||
source: "/template/nft-drop",
|
||||
destination: "/template/erc721",
|
||||
@@ -121,17 +160,12 @@ function redirects() {
|
||||
},
|
||||
{
|
||||
source: "/create-api-key",
|
||||
destination: "/dashboard/settings/api-keys",
|
||||
destination: "/team/~/~/projects",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/settings",
|
||||
destination: "/dashboard/settings/api-keys",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/connect",
|
||||
destination: "/dashboard/connect/analytics",
|
||||
destination: "/team/~/~/projects",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
@@ -139,61 +173,6 @@ function redirects() {
|
||||
destination: "https://playground.thirdweb.com/connect/sign-in/button",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/wallet",
|
||||
destination: "https://playground.thirdweb.com/connect/sign-in/button",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/rpc",
|
||||
destination: "/dashboard/infrastructure/rpc-edge",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/storage",
|
||||
destination: "/dashboard/infrastructure/storage",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/smart-wallet",
|
||||
destination: "/account-abstraction",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/published",
|
||||
destination: "/dashboard/publish",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/wallets",
|
||||
destination: "https://playground.thirdweb.com/connect/sign-in/button",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/wallets/analytics",
|
||||
destination: "/dashboard/connect/analytics",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/wallets/embedded",
|
||||
destination: "/dashboard/connect/embedded-wallets",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/wallets/smart-wallet",
|
||||
destination: "/dashboard/connect/account-abstraction",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/wallets/connect",
|
||||
destination: "https://playground.thirdweb.com/connect/sign-in/button",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/infrastructure",
|
||||
destination: "/dashboard/settings/storage",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/infrastructure/storage",
|
||||
destination: "/dashboard/settings/storage",
|
||||
@@ -204,26 +183,6 @@ function redirects() {
|
||||
destination: "/chainlist",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/contracts",
|
||||
destination: "/dashboard/contracts/deploy",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/solidity-sdk",
|
||||
destination: "/build",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/connect-wallet",
|
||||
destination: "/connect",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/wallet-sdk",
|
||||
destination: "https://portal.thirdweb.com/wallet-sdk/latest",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/solutions/commerce",
|
||||
destination: "/solutions/loyalty",
|
||||
@@ -244,27 +203,12 @@ function redirects() {
|
||||
destination: "/optimism",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/payments/settings",
|
||||
destination: "/dashboard/connect/pay",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/payments/contracts",
|
||||
destination: "/dashboard/connect/pay",
|
||||
permanent: false,
|
||||
},
|
||||
// Redirecting as ambassadors lives in community now
|
||||
{
|
||||
source: "/ambassadors",
|
||||
destination: "/community/ambassadors",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/dashboard/connect/embedded-wallets",
|
||||
destination: "/dashboard/connect/in-app-wallets",
|
||||
permanent: false,
|
||||
},
|
||||
{
|
||||
source: "/embedded-wallets",
|
||||
destination: "/in-app-wallets",
|
||||
@@ -321,6 +265,7 @@ function redirects() {
|
||||
destination: "/account-abstraction",
|
||||
permanent: false,
|
||||
},
|
||||
...legacyDashboardToTeamRedirects,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -13,13 +13,8 @@ export type Team = {
|
||||
bannedAt?: string;
|
||||
image?: string;
|
||||
billingPlan: "pro" | "growth" | "free";
|
||||
billingStatus: "validPayment" | (string & {}); // what's the other value?
|
||||
billingEmail: string;
|
||||
// billingExternalId: string;
|
||||
// billingType: "STRIPE" | ??
|
||||
// billingCustomerPayload: ?? | null
|
||||
// paymentAttemptCount: number
|
||||
// trialPeriodEndedAt: string | null;
|
||||
billingStatus: "validPayment" | (string & {}) | null;
|
||||
billingEmail: string | null;
|
||||
};
|
||||
|
||||
export async function getTeamBySlug(slug: string) {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { setCookie } from "../../stores/SyncStoreToCookies";
|
||||
|
||||
export function DashboardTypeCookieSetter(props: {
|
||||
type: "team" | "old";
|
||||
}) {
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
useEffect(() => {
|
||||
setCookie("x-dashboard-type", props.type);
|
||||
}, [props.type]);
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { useState } from "react";
|
||||
import { BadgeContainer } from "../../../../stories/utils";
|
||||
import { getThirdwebClient } from "../../../constants/thirdweb.server";
|
||||
import { Button } from "../../ui/button";
|
||||
import { GradientAvatar } from "./GradientAvatar";
|
||||
|
||||
@@ -17,29 +18,74 @@ export const Desktop: Story = {
|
||||
args: {},
|
||||
};
|
||||
|
||||
const client = getThirdwebClient();
|
||||
|
||||
function Story() {
|
||||
return (
|
||||
<div className="flex flex-col gap-10 p-10">
|
||||
<p> All images below are set with size-6 className </p>
|
||||
|
||||
<BadgeContainer label="No Src, No id - Skeleton">
|
||||
<GradientAvatar id={undefined} src={undefined} className="size-6" />
|
||||
<GradientAvatar
|
||||
id={undefined}
|
||||
src={undefined}
|
||||
className="size-6"
|
||||
client={client}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="No Src, id=foo - Skeleton">
|
||||
<GradientAvatar id={"foo"} src={undefined} className="size-6" />
|
||||
<GradientAvatar
|
||||
id={"foo"}
|
||||
src={undefined}
|
||||
className="size-6"
|
||||
client={client}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="Invalid/Empty Src, id=foo - Gradient">
|
||||
<GradientAvatar id={"foo"} src={""} className="size-6" />
|
||||
<BadgeContainer label="Invalid Src, id=foo - Gradient">
|
||||
<GradientAvatar
|
||||
id={"foo"}
|
||||
src={""}
|
||||
className="size-6"
|
||||
client={client}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="Invalid/Empty Src, id=bar - Gradient">
|
||||
<GradientAvatar id={"bar"} src={""} className="size-6" />
|
||||
<BadgeContainer label="Invalid Src, id=bar - Gradient">
|
||||
<GradientAvatar
|
||||
id={"bar"}
|
||||
src={""}
|
||||
className="size-6"
|
||||
client={client}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="Empty/Invalid Src, No id - default fallback">
|
||||
<GradientAvatar src="invalid-src" id={undefined} className="size-6" />
|
||||
<GradientAvatar
|
||||
src="invalid-src"
|
||||
id={undefined}
|
||||
className="size-6"
|
||||
client={client}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="Valid Src, https">
|
||||
<GradientAvatar
|
||||
src="https://picsum.photos/200/300"
|
||||
id={undefined}
|
||||
className="size-6"
|
||||
client={client}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="Valid Src, ipfs">
|
||||
<GradientAvatar
|
||||
src="ipfs://QmZbeJYEs7kCJHyQxjxU2SJUtjSAr4m87wzJFJUyWomKdj/Smily.svg"
|
||||
id={undefined}
|
||||
className="size-6"
|
||||
client={client}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<ToggleTest />
|
||||
@@ -74,7 +120,12 @@ function ToggleTest() {
|
||||
<p> Src+ID is: {data ? "set" : "not set"} </p>
|
||||
|
||||
<BadgeContainer label="Valid Src">
|
||||
<GradientAvatar src={data?.src} id={data?.id} className="size-6" />
|
||||
<GradientAvatar
|
||||
src={data?.src}
|
||||
id={data?.id}
|
||||
className="size-6"
|
||||
client={client}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
<BadgeContainer label="invalid Src">
|
||||
@@ -82,6 +133,7 @@ function ToggleTest() {
|
||||
className="size-6"
|
||||
src={data ? "invalid-src" : undefined}
|
||||
id={undefined}
|
||||
client={client}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Img } from "@/components/blocks/Img";
|
||||
import { useMemo } from "react";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import { resolveSchemeWithErrorHandler } from "../../../lib/resolveSchemeWithErrorHandler";
|
||||
import { cn } from "../../../lib/utils";
|
||||
|
||||
const gradients = [
|
||||
@@ -32,6 +34,7 @@ export function GradientAvatar(props: {
|
||||
src: string | undefined;
|
||||
id: string | undefined;
|
||||
className: string;
|
||||
client: ThirdwebClient;
|
||||
}) {
|
||||
const gradient = useMemo(() => {
|
||||
if (!props.id) {
|
||||
@@ -40,9 +43,16 @@ export function GradientAvatar(props: {
|
||||
return getGradientForString(props.id);
|
||||
}, [props.id]);
|
||||
|
||||
const resolvedSrc = props.src
|
||||
? resolveSchemeWithErrorHandler({
|
||||
client: props.client,
|
||||
uri: props.src,
|
||||
})
|
||||
: props.src;
|
||||
|
||||
return (
|
||||
<Img
|
||||
src={props.src}
|
||||
src={resolvedSrc}
|
||||
className={cn("rounded-full", props.className)}
|
||||
fallback={
|
||||
gradient ? (
|
||||
|
||||
@@ -51,7 +51,8 @@ export function RenderSidebarLinks(props: { links: SidebarLink[] }) {
|
||||
{props.links.map((link, i) => {
|
||||
if ("group" in link) {
|
||||
return (
|
||||
<div className={cn({ "mt-6": i !== 0 })}>
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: items won't be reordered
|
||||
<div className={cn({ "mt-6": i !== 0 })} key={i}>
|
||||
<p className={cn("px-3 py-2 font-medium text-foreground")}>
|
||||
{link.group}
|
||||
</p>
|
||||
@@ -63,7 +64,8 @@ export function RenderSidebarLinks(props: { links: SidebarLink[] }) {
|
||||
const isExternal = link.href.startsWith("http");
|
||||
return (
|
||||
<NavLink
|
||||
key={link.href}
|
||||
// biome-ignore lint/suspicious/noArrayIndexKey: items won't be reordered
|
||||
key={i}
|
||||
href={link.href}
|
||||
className="flex items-center gap-2 rounded-md px-3 py-2 text-muted-foreground text-sm hover:bg-muted"
|
||||
activeClassName="text-foreground"
|
||||
|
||||
@@ -19,7 +19,7 @@ export function AppFooter(props: AppFooterProps) {
|
||||
return (
|
||||
<footer
|
||||
className={cn(
|
||||
"w-full border-border border-t bg-white py-6 md:py-8 dark:bg-muted/50",
|
||||
"w-full border-border border-t bg-muted/50 py-6 md:py-8",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -50,7 +50,7 @@ export function DatePickerWithRange(props: {
|
||||
id="date"
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"justify-start gap-2 bg-card text-left font-normal",
|
||||
"justify-start gap-2 text-left font-normal",
|
||||
props.className,
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -35,40 +35,43 @@ const DialogContent = React.forwardRef<
|
||||
dialogOverlayClassName?: string;
|
||||
dialogCloseClassName?: string;
|
||||
}
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<DialogPortal>
|
||||
<DialogOverlay className={props.dialogOverlayClassName} />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed grid w-full gap-4 rounded-t-xl border border-border bg-background p-6 shadow-lg duration-300 md:max-w-lg md:rounded-lg",
|
||||
// on mobile - put the dialog at the bottom of the screen, animate - slide up and fade in
|
||||
"right-0 bottom-0 left-0",
|
||||
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||
// on desktop - vertically and horizontally center the dialog, animate - zoom and fade in
|
||||
"md:top-[50%] md:bottom-auto md:left-[50%] md:translate-x-[-50%] md:translate-y-[-50%] ",
|
||||
"md:data-[state=closed]:zoom-out-95 md:data-[state=open]:zoom-in-95 ",
|
||||
"md:data-[state=closed]:slide-out-to-left-1/2 md:data-[state=closed]:slide-out-to-top-[48%] ",
|
||||
"md:data-[state=open]:slide-in-from-left-1/2 md:data-[state=open]:slide-in-from-top-[48%] ",
|
||||
"data-[state=closed]:animate-out data-[state=open]:animate-in",
|
||||
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<DialogPrimitive.Close
|
||||
>(({ className, children, ...props }, ref) => {
|
||||
const { dialogOverlayClassName, dialogCloseClassName, ...restProps } = props;
|
||||
return (
|
||||
<DialogPortal>
|
||||
<DialogOverlay className={dialogOverlayClassName} />
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",
|
||||
props.dialogCloseClassName,
|
||||
"fixed grid w-full gap-4 rounded-t-xl border border-border bg-background p-6 shadow-lg duration-300 md:max-w-lg md:rounded-lg",
|
||||
// on mobile - put the dialog at the bottom of the screen, animate - slide up and fade in
|
||||
"right-0 bottom-0 left-0",
|
||||
"data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
|
||||
// on desktop - vertically and horizontally center the dialog, animate - zoom and fade in
|
||||
"md:top-[50%] md:bottom-auto md:left-[50%] md:translate-x-[-50%] md:translate-y-[-50%] ",
|
||||
"md:data-[state=closed]:zoom-out-95 md:data-[state=open]:zoom-in-95 ",
|
||||
"md:data-[state=closed]:slide-out-to-left-1/2 md:data-[state=closed]:slide-out-to-top-[48%] ",
|
||||
"md:data-[state=open]:slide-in-from-left-1/2 md:data-[state=open]:slide-in-from-top-[48%] ",
|
||||
"data-[state=closed]:animate-out data-[state=open]:animate-in",
|
||||
"data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className,
|
||||
)}
|
||||
{...restProps}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
));
|
||||
{children}
|
||||
<DialogPrimitive.Close
|
||||
className={cn(
|
||||
"absolute top-4 right-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground",
|
||||
dialogCloseClassName,
|
||||
)}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
<span className="sr-only">Close</span>
|
||||
</DialogPrimitive.Close>
|
||||
</DialogPrimitive.Content>
|
||||
</DialogPortal>
|
||||
);
|
||||
});
|
||||
DialogContent.displayName = DialogPrimitive.Content.displayName;
|
||||
|
||||
const DialogHeader = ({
|
||||
|
||||
@@ -197,6 +197,7 @@ export function TabPathLinks(props: {
|
||||
isActive: l.exactMatch
|
||||
? pathname === l.path
|
||||
: pathname.startsWith(l.path),
|
||||
isDisabled: l.isDisabled,
|
||||
}))}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -2,8 +2,8 @@ export function isLoginRequired(pathname: string) {
|
||||
// remove '/' in front and then split by '/'
|
||||
const paths = pathname.slice(1).split("/");
|
||||
|
||||
// /dashboard, /team, /cli
|
||||
if (paths[0] === "dashboard" || paths[0] === "team" || paths[0] === "cli") {
|
||||
// /team, /cli
|
||||
if (paths[0] === "team" || paths[0] === "cli") {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
:root,
|
||||
[data-theme="light"] {
|
||||
/* bg - neutral */
|
||||
--background: 0 0% 98%;
|
||||
--background: 0 0% 100%;
|
||||
--popover: 0 0% 100%;
|
||||
--card: 0 0% 100%;
|
||||
--secondary: 0 0% 92%;
|
||||
@@ -46,7 +46,7 @@
|
||||
[data-theme="dark"] {
|
||||
/* bg - neutral */
|
||||
--background: 0 0% 0%;
|
||||
--card: 0 0% 5%;
|
||||
--card: 0 0% 0%;
|
||||
--popover: 0 0% 0%;
|
||||
--secondary: 0 0% 10%;
|
||||
--muted: 0 0% 9%;
|
||||
|
||||
@@ -15,7 +15,6 @@ import { usePathname } from "next/navigation";
|
||||
import { useCallback, useMemo, useState } from "react";
|
||||
import type { Chain } from "thirdweb";
|
||||
import {
|
||||
AutoConnect,
|
||||
ConnectButton,
|
||||
type NetworkSelectorProps,
|
||||
useConnectModal,
|
||||
@@ -126,8 +125,6 @@ export const CustomConnectWallet = (props: {
|
||||
<div className="flex h-[48px] w-[144px] items-center justify-center rounded-lg border border-border bg-muted">
|
||||
<Spinner className="size-4" />
|
||||
</div>
|
||||
{/* need auto connect here so that we actually connect */}
|
||||
<AutoConnect client={thirdwebClient} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -147,8 +144,6 @@ export const CustomConnectWallet = (props: {
|
||||
Sign In
|
||||
</Link>
|
||||
</Button>
|
||||
{/* need auto connect here so that we actually connect */}
|
||||
<AutoConnect client={thirdwebClient} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import type { ResultItem } from "app/team/[team_slug]/[project_slug]/engine/(instance)/[engineId]/metrics/components/StatusCodes";
|
||||
import type { ResultItem } from "app/team/[team_slug]/(team)/~/engine/(instance)/[engineId]/metrics/components/StatusCodes";
|
||||
import { THIRDWEB_API_HOST } from "constants/urls";
|
||||
import type { EngineBackendWalletType } from "lib/engine";
|
||||
import { useState } from "react";
|
||||
|
||||
+1
-7
@@ -1,6 +1,4 @@
|
||||
"use client";
|
||||
|
||||
import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
|
||||
import { Spinner } from "@/components/ui/Spinner/Spinner";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Form, FormControl, FormField, FormItem } from "@/components/ui/form";
|
||||
@@ -215,11 +213,7 @@ export function FaucetButton({
|
||||
Verify your Email
|
||||
</Button>
|
||||
{/* We will show the modal only if the user click on it, because this is a public page */}
|
||||
{showOnboarding && (
|
||||
<ChakraProviderSetup>
|
||||
<Onboarding onOpenChange={setShowOnBoarding} />
|
||||
</ChakraProviderSetup>
|
||||
)}
|
||||
{showOnboarding && <Onboarding onOpenChange={setShowOnBoarding} />}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ export function ChainHeader(props: ChainHeaderProps) {
|
||||
}
|
||||
/>
|
||||
<Button variant="default" asChild>
|
||||
<Link href="https://thirdweb.com/dashboard" target="_blank">
|
||||
<Link href="/team" target="_blank">
|
||||
Get started with thirdweb
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
@@ -205,7 +205,7 @@ export default async function ChainPageLayout(props: {
|
||||
}
|
||||
/>
|
||||
<Button variant="primary">
|
||||
<Link href="https://thirdweb.com/dashboard" target="_blank">
|
||||
<Link href="/team" target="_blank">
|
||||
Get started
|
||||
</Link>
|
||||
</Button>
|
||||
|
||||
-1
@@ -304,7 +304,6 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
|
||||
return (
|
||||
<Flex gap={8} direction="column">
|
||||
<LazyCreateAPIKeyDialog
|
||||
wording="api-key"
|
||||
prefill={{
|
||||
name: "Embed API Key",
|
||||
domains: "embed.ipfscdn.io",
|
||||
|
||||
@@ -109,7 +109,7 @@ const OP_CTA = {
|
||||
description:
|
||||
"Successful applicants receive gas grants for use across all supported Optimism Superchain networks. These grants can sponsor gas fees for any onchain activity using our Account Abstraction tools.",
|
||||
buttonText: "Apply now",
|
||||
buttonLink: "/dashboard/settings/gas-credits",
|
||||
buttonLink: "/team/~/~/settings/credits",
|
||||
} satisfies ChainCTAProps;
|
||||
|
||||
const chainMetaRecord = {
|
||||
|
||||
+2
-1
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { InlineCode } from "@/components/ui/inline-code";
|
||||
import { TableContainer } from "@/components/ui/table";
|
||||
import {
|
||||
@@ -15,7 +17,6 @@ import { useTrack } from "hooks/analytics/useTrack";
|
||||
import { useRpcValidation } from "hooks/chains/useRpcValidation";
|
||||
import { AlertCircleIcon, CheckCircleIcon } from "lucide-react";
|
||||
import { type ChangeEvent, useState } from "react";
|
||||
|
||||
import { Button, Card, FormLabel, Link } from "tw-components";
|
||||
|
||||
const StatusCheck = (props: {
|
||||
@@ -0,0 +1,41 @@
|
||||
import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
|
||||
import type { Metadata } from "next";
|
||||
import Link from "next/link";
|
||||
import ChainValidation from "./ChainValidation";
|
||||
|
||||
const title = "Validate Chain";
|
||||
const description = "Validate a given chain is compatible with thirdweb.";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
robots: "noindex, nofollow",
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<ChakraProviderSetup>
|
||||
<div className="container py-6">
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="font-semibold text-2xl tracking-tight">
|
||||
Validate Chain
|
||||
</h1>
|
||||
<p>
|
||||
Validate a given chain is compatible with{" "}
|
||||
<Link
|
||||
href="/chainlist"
|
||||
className="text-link-foreground hover:text-foreground"
|
||||
>
|
||||
thirdweb
|
||||
</Link>
|
||||
.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<ChainValidation />
|
||||
</div>
|
||||
</div>
|
||||
</ChakraProviderSetup>
|
||||
);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
import { SidebarLayout } from "@/components/blocks/SidebarLayout";
|
||||
|
||||
export function ConnectSidebarLayout(props: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<SidebarLayout
|
||||
sidebarLinks={[
|
||||
{
|
||||
label: "Analytics",
|
||||
href: "/dashboard/connect/analytics",
|
||||
},
|
||||
{
|
||||
label: "In-App Wallets",
|
||||
href: "/dashboard/connect/in-app-wallets",
|
||||
},
|
||||
{
|
||||
label: "Ecosystem Wallets",
|
||||
href: "/dashboard/connect/ecosystem",
|
||||
},
|
||||
{
|
||||
label: "Account Abstraction",
|
||||
href: "/dashboard/connect/account-abstraction",
|
||||
},
|
||||
{
|
||||
label: "Pay",
|
||||
href: "/dashboard/connect/pay",
|
||||
},
|
||||
{
|
||||
label: "Playground",
|
||||
href: "https://playground.thirdweb.com/connect/sign-in/button",
|
||||
},
|
||||
]}
|
||||
>
|
||||
{props.children}
|
||||
</SidebarLayout>
|
||||
);
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import type { ApiKey } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { ApiKeysMenu } from "../../../../../components/settings/ApiKeys/Menu";
|
||||
|
||||
export function AccountAbstractionAPIKeysMenu(props: {
|
||||
apiKeys: Pick<ApiKey, "name" | "key">[];
|
||||
selectedAPIKey: Pick<ApiKey, "name" | "key">;
|
||||
}) {
|
||||
const router = useDashboardRouter();
|
||||
return (
|
||||
<ApiKeysMenu
|
||||
apiKeys={props.apiKeys}
|
||||
selectedKey={props.selectedAPIKey}
|
||||
onSelect={(key) => {
|
||||
router.push(`/dashboard/connect/account-abstraction/${key.key}`);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||
import { CircleAlertIcon } from "lucide-react";
|
||||
import { useActiveWalletChain } from "thirdweb/react";
|
||||
import { isOpChainId } from "../../../../team/[team_slug]/[project_slug]/connect/account-abstraction/isOpChain";
|
||||
|
||||
export function OpChainAlert() {
|
||||
const chain = useActiveWalletChain();
|
||||
const isOpChain = chain?.id ? isOpChainId(chain.id) : false;
|
||||
|
||||
if (!isOpChain) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert variant="info">
|
||||
<CircleAlertIcon className="size-4" />
|
||||
<AlertTitle>Using the gas credits for OP chain paymaster</AlertTitle>
|
||||
<AlertDescription>
|
||||
Credits will automatically be applied to cover gas fees for any onchain
|
||||
activity across thirdweb services. <br />
|
||||
Eligible chains: OP Mainnet, Base, Zora, Frax, Mode.
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
);
|
||||
}
|
||||
-25
@@ -1,25 +0,0 @@
|
||||
import { TrackedLinkTW } from "@/components/ui/tracked-link";
|
||||
|
||||
export function PageHeader() {
|
||||
return (
|
||||
<div className="flex flex-col gap-1.5">
|
||||
<h1 className="font-semibold text-2xl tracking-tight lg:text-3xl">
|
||||
Account Abstraction
|
||||
</h1>
|
||||
|
||||
<p className="text-muted-foreground text-sm">
|
||||
Easily integrate Account abstraction (ERC-4337) compliant smart accounts
|
||||
into your apps.{" "}
|
||||
<TrackedLinkTW
|
||||
target="_blank"
|
||||
label="docs-wallets"
|
||||
category="smart-wallet"
|
||||
href="https://portal.thirdweb.com/wallets/smart-wallet"
|
||||
className="text-link-foreground hover:text-foreground"
|
||||
>
|
||||
View Documentation
|
||||
</TrackedLinkTW>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-79
@@ -1,79 +0,0 @@
|
||||
import { accountStatus } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { SmartWalletsBillingAlert } from "components/settings/ApiKeys/Alerts";
|
||||
import { ConnectSDKCard } from "components/shared/ConnectSDKCard";
|
||||
import { SmartWallets } from "components/smart-wallets";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getAccount } from "../../../../../account/settings/getAccount";
|
||||
import { AccountAbstractionAPIKeysMenu } from "../AccountAbstractionAPIKeysMenu";
|
||||
import { OpChainAlert } from "../OpChainAlert";
|
||||
import { PageHeader } from "../PageHeader";
|
||||
import { getAASupportedAPIKeys } from "../getAASupportedAPIKeys";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{
|
||||
clientId: string;
|
||||
}>;
|
||||
searchParams: Promise<{
|
||||
tab?: string;
|
||||
}>;
|
||||
}) {
|
||||
const { clientId } = await props.params;
|
||||
const dashboardAccount = await getAccount();
|
||||
|
||||
if (!dashboardAccount) {
|
||||
redirect(
|
||||
`/login?next=${encodeURIComponent(
|
||||
`/dashboard/connect/account-abstraction/${clientId}`,
|
||||
)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const apiKeys = await getAASupportedAPIKeys();
|
||||
const apiKey = apiKeys.find((key) => key.key === clientId);
|
||||
|
||||
if (!apiKey) {
|
||||
redirect("/dashboard/connect/account-abstraction");
|
||||
}
|
||||
|
||||
const hasSmartWalletsWithoutBilling = apiKeys.find((k) =>
|
||||
k.services?.find(
|
||||
(s) =>
|
||||
dashboardAccount.status !== accountStatus.validPayment &&
|
||||
s.name === "bundler",
|
||||
),
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-6">
|
||||
<div className="flex flex-col content-start justify-between gap-4 lg:flex-row">
|
||||
<PageHeader />
|
||||
|
||||
<div>
|
||||
<AccountAbstractionAPIKeysMenu
|
||||
apiKeys={apiKeys.map((x) => ({
|
||||
name: x.name,
|
||||
key: x.key,
|
||||
}))}
|
||||
selectedAPIKey={apiKey}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{hasSmartWalletsWithoutBilling ? (
|
||||
<SmartWalletsBillingAlert />
|
||||
) : (
|
||||
<OpChainAlert />
|
||||
)}
|
||||
<SmartWallets
|
||||
apiKeyServices={apiKey.services || []}
|
||||
trackingCategory="smart-wallet"
|
||||
tab={(await props.searchParams).tab}
|
||||
smartWalletsLayoutSlug={`/dashboard/connect/account-abstraction/${clientId}`}
|
||||
clientId={apiKey.key}
|
||||
/>
|
||||
<ConnectSDKCard
|
||||
title="Get Started"
|
||||
description="Add account abstraction to your app with the Connect SDK."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
import { getApiKeys } from "../../../../api/lib/getAPIKeys";
|
||||
|
||||
export async function getAASupportedAPIKeys() {
|
||||
return (await getApiKeys()).filter((key) => {
|
||||
return !!(key.services || []).find((srv) => srv.name === "bundler");
|
||||
});
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
|
||||
import type { Metadata } from "next";
|
||||
import { getAbsoluteUrl } from "../../../../../lib/vercel-utils";
|
||||
|
||||
export default function Layout(props: { children: React.ReactNode }) {
|
||||
return <ChakraProviderSetup>{props.children}</ChakraProviderSetup>;
|
||||
}
|
||||
|
||||
const seo = {
|
||||
title: "The Complete Account Abstraction Toolkit | thirdweb",
|
||||
desc: "Add account abstraction to your web3 app & unlock powerful features for seamless onboarding, customizable transactions, & maximum security. Get started.",
|
||||
};
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: seo.title,
|
||||
description: seo.desc,
|
||||
openGraph: {
|
||||
title: seo.title,
|
||||
description: seo.desc,
|
||||
images: [
|
||||
{
|
||||
url: `${getAbsoluteUrl()}/assets/og-image/dashboard-wallets-smart-wallet.png`,
|
||||
width: 1200,
|
||||
height: 630,
|
||||
alt: seo.title,
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
@@ -1,36 +0,0 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { NoApiKeys } from "../../../../../components/settings/ApiKeys/NoApiKeys";
|
||||
import { ConnectSDKCard } from "../../../../../components/shared/ConnectSDKCard";
|
||||
import { getAuthToken } from "../../../../api/lib/getAuthToken";
|
||||
import { PageHeader } from "./PageHeader";
|
||||
import { getAASupportedAPIKeys } from "./getAASupportedAPIKeys";
|
||||
|
||||
export default async function Page() {
|
||||
const authToken = await getAuthToken();
|
||||
|
||||
if (!authToken) {
|
||||
redirect(
|
||||
`/login?next=${encodeURIComponent("/dashboard/connect/account-abstraction")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const apiKeys = await getAASupportedAPIKeys();
|
||||
const firstKey = apiKeys[0];
|
||||
|
||||
if (firstKey) {
|
||||
redirect(`/dashboard/connect/account-abstraction/${firstKey.key}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader />
|
||||
<div className="h-8" />
|
||||
<NoApiKeys service="Account Abstraction" />
|
||||
<div className="h-10" />
|
||||
<ConnectSDKCard
|
||||
title="Get Started"
|
||||
description="Add account abstraction to your app with the Connect SDK."
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import type { ApiKey } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { useDashboardRouter } from "../../../../../../@/lib/DashboardRouter";
|
||||
import { ApiKeysMenu } from "../../../../../../components/settings/ApiKeys/Menu";
|
||||
|
||||
export function AnalyticsPageAPIKeysMenu(props: {
|
||||
apiKeys: Pick<ApiKey, "name" | "key">[];
|
||||
selectedAPIKey: Pick<ApiKey, "name" | "key">;
|
||||
}) {
|
||||
const router = useDashboardRouter();
|
||||
return (
|
||||
<ApiKeysMenu
|
||||
apiKeys={props.apiKeys}
|
||||
selectedKey={props.selectedAPIKey}
|
||||
onSelect={(key) => {
|
||||
router.push(`/dashboard/connect/analytics/${key.key}`);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { ConnectSDKCard } from "../../../../../../components/shared/ConnectSDKCard";
|
||||
import { getApiKeys } from "../../../../../api/lib/getAPIKeys";
|
||||
import { getAuthToken } from "../../../../../api/lib/getAuthToken";
|
||||
import { ConnectAnalyticsDashboard } from "../../../../../team/[team_slug]/[project_slug]/connect/analytics/ConnectAnalyticsDashboard";
|
||||
import { AnalyticsPageAPIKeysMenu } from "./AnalyticsPageAPIKeysMenu";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{
|
||||
clientId: string;
|
||||
}>;
|
||||
}) {
|
||||
const authToken = await getAuthToken();
|
||||
const { clientId } = await props.params;
|
||||
|
||||
if (!authToken) {
|
||||
redirect(
|
||||
`/login?next=${encodeURIComponent(`/dashboard/connect/analytics/${clientId}`)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const _apiKeys = await getApiKeys();
|
||||
// slimmed down version to reduce client side payload
|
||||
const apiKeys = _apiKeys.map((key) => ({
|
||||
name: key.name,
|
||||
key: key.key,
|
||||
}));
|
||||
|
||||
const apiKey = apiKeys.find((key) => key.key === clientId);
|
||||
|
||||
if (!apiKey) {
|
||||
redirect("/dashboard/connect/analytics");
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<h1 className="mb-1 font-semibold text-3xl tracking-tight">
|
||||
Connect Analytics
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Visualize how users are connecting to your apps.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<AnalyticsPageAPIKeysMenu apiKeys={apiKeys} selectedAPIKey={apiKey} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-4 lg:h-8" />
|
||||
|
||||
<ConnectAnalyticsDashboard
|
||||
clientId={apiKey.key}
|
||||
connectLayoutSlug="/dashboard/connect"
|
||||
/>
|
||||
|
||||
<div className="h-4 lg:h-8" />
|
||||
<ConnectSDKCard description="Add the Connect SDK to your app to start collecting analytics." />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
@@ -1,61 +0,0 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Link from "next/link";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ConnectSDKCard } from "../../../../../components/shared/ConnectSDKCard";
|
||||
import { getApiKeys } from "../../../../api/lib/getAPIKeys";
|
||||
import { getAuthToken } from "../../../../api/lib/getAuthToken";
|
||||
|
||||
// base page
|
||||
// - if no keys -> show no keys page
|
||||
// - else redirect to first key
|
||||
|
||||
export default async function Page() {
|
||||
const authToken = await getAuthToken();
|
||||
|
||||
if (!authToken) {
|
||||
redirect(
|
||||
`/login?next=${encodeURIComponent("/dashboard/connect/analytics")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const apiKeys = await getApiKeys();
|
||||
const firstKey = apiKeys[0];
|
||||
|
||||
if (firstKey) {
|
||||
redirect(`/dashboard/connect/analytics/${firstKey.key}`);
|
||||
}
|
||||
|
||||
return <NoKeysCreatedPage />;
|
||||
}
|
||||
|
||||
function NoKeysCreatedPage() {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:items-center lg:justify-between">
|
||||
<div>
|
||||
<h1 className="mb-1 font-semibold text-3xl tracking-tight">
|
||||
Connect Analytics
|
||||
</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Visualize how users are connecting to your apps.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-4 lg:h-8" />
|
||||
|
||||
<div className="flex flex-col items-center rounded-lg border border-border bg-muted/50 px-4 py-10 lg:px-6">
|
||||
<h3 className="mb-3 font-semibold text-2xl">No API keys found</h3>
|
||||
<p className="mb-6 text-muted-foreground text-sm">
|
||||
Start using the Connect SDK in your app with a free API key.
|
||||
</p>
|
||||
<Button asChild variant="primary">
|
||||
<Link href="/dashboard/settings/api-keys">Create API Key</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="h-4 lg:h-8" />
|
||||
<ConnectSDKCard description="Add the Connect SDK to your app to start collecting analytics." />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
import { EcosystemLayoutSlug } from "./components/EcosystemSlugLayout";
|
||||
|
||||
export default async function Layout(props: {
|
||||
children: React.ReactNode;
|
||||
params: Promise<{ slug: string }>;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
|
||||
const { children } = props;
|
||||
|
||||
return (
|
||||
<EcosystemLayoutSlug
|
||||
params={params}
|
||||
ecosystemLayoutPath="/dashboard/connect/ecosystem"
|
||||
>
|
||||
{children}
|
||||
</EcosystemLayoutSlug>
|
||||
);
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
@@ -1,3 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
@@ -1,7 +0,0 @@
|
||||
import { EcosystemCreatePage } from "./EcosystemCreatePage";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<EcosystemCreatePage ecosystemLayoutPath="/dashboard/connect/ecosystem" />
|
||||
);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
@@ -1,7 +0,0 @@
|
||||
import { EcosystemLandingPage } from "./EcosystemLandingPage";
|
||||
|
||||
export default function Page() {
|
||||
return (
|
||||
<EcosystemLandingPage ecosystemLayoutPath="/dashboard/connect/ecosystem" />
|
||||
);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import { TrackedLinkTW } from "@/components/ui/tracked-link";
|
||||
|
||||
export function PageHeader() {
|
||||
return (
|
||||
<div>
|
||||
<h1 className="font-semibold text-3xl tracking-tigher">In-App Wallets</h1>
|
||||
<div className="h-3" />
|
||||
<p className="max-w-[500px] text-muted-foreground ">
|
||||
A wallet infrastructure that enables apps to create, manage, and control
|
||||
their users wallets. Email login, social login, and bring-your-own auth
|
||||
supported.{" "}
|
||||
<TrackedLinkTW
|
||||
target="_blank"
|
||||
href="https://portal.thirdweb.com/connect/in-app-wallet/overview"
|
||||
label="learn-more"
|
||||
category="embedded-wallet"
|
||||
className="text-link-foreground hover:text-foreground"
|
||||
>
|
||||
Learn more
|
||||
</TrackedLinkTW>
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-32
@@ -1,32 +0,0 @@
|
||||
"use client";
|
||||
import { TabLinks } from "@/components/ui/tabs";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export function Tabs({ clientId }: { clientId: string }) {
|
||||
const path = usePathname();
|
||||
|
||||
return (
|
||||
<TabLinks
|
||||
links={[
|
||||
{
|
||||
name: "Analytics",
|
||||
href: `/dashboard/connect/in-app-wallets/${clientId}/analytics`,
|
||||
isActive: path?.endsWith("/analytics") || false,
|
||||
isDisabled: false,
|
||||
},
|
||||
{
|
||||
name: "Users",
|
||||
href: `/dashboard/connect/in-app-wallets/${clientId}/users`,
|
||||
isActive: path?.endsWith("/users") || false,
|
||||
isDisabled: false,
|
||||
},
|
||||
{
|
||||
name: "Configuration",
|
||||
href: `/dashboard/connect/in-app-wallets/${clientId}/config`,
|
||||
isActive: path?.endsWith("/config") || false,
|
||||
isDisabled: false,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
-1
@@ -1 +0,0 @@
|
||||
export const TRACKING_CATEGORY = "embedded-wallet";
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
-37
@@ -1,37 +0,0 @@
|
||||
import type { Range } from "components/analytics/date-range-selector";
|
||||
import { InAppWalletAnalytics } from "components/embedded-wallets/Analytics";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{ clientId: string }>;
|
||||
searchParams: Promise<{
|
||||
from?: string;
|
||||
to?: string;
|
||||
type?: string;
|
||||
interval?: string;
|
||||
}>;
|
||||
}) {
|
||||
const searchParams = await props.searchParams;
|
||||
const params = await props.params;
|
||||
const range =
|
||||
searchParams.from && searchParams.to
|
||||
? {
|
||||
type: searchParams.type ?? "last-120",
|
||||
from: new Date(searchParams.from),
|
||||
to: new Date(searchParams.to),
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const interval: "day" | "week" = ["day", "week"].includes(
|
||||
searchParams.interval ?? "",
|
||||
)
|
||||
? (searchParams.interval as "day" | "week")
|
||||
: "week";
|
||||
|
||||
return (
|
||||
<InAppWalletAnalytics
|
||||
clientId={params.clientId}
|
||||
interval={interval}
|
||||
range={range as Range}
|
||||
/>
|
||||
);
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
import { InAppWalletSettingsPage } from "components/embedded-wallets/Configure";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getInAppWalletSupportedAPIKeys } from "../../getInAppWalletSupportedAPIKeys";
|
||||
import { TRACKING_CATEGORY } from "../_constants";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{ clientId: string }>;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
const { clientId } = params;
|
||||
|
||||
const apiKeys = await getInAppWalletSupportedAPIKeys();
|
||||
const apiKey = apiKeys.find((key) => key.key === clientId);
|
||||
|
||||
if (!apiKey) {
|
||||
redirect("/dashboard/connect/in-app-wallets");
|
||||
}
|
||||
return (
|
||||
<InAppWalletSettingsPage
|
||||
apiKey={apiKey}
|
||||
trackingCategory={TRACKING_CATEGORY}
|
||||
/>
|
||||
);
|
||||
}
|
||||
-87
@@ -1,87 +0,0 @@
|
||||
import { InAppWalletsSummary } from "components/embedded-wallets/Analytics/Summary";
|
||||
import { getInAppWalletUsage } from "data/analytics/wallets/in-app";
|
||||
import { subDays } from "date-fns";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getAuthToken } from "../../../../../api/lib/getAuthToken";
|
||||
import { InAppWaletFooterSection } from "../../../../../team/[team_slug]/[project_slug]/connect/in-app-wallets/_components/footer";
|
||||
import { PageHeader } from "../PageHeader";
|
||||
import { getInAppWalletSupportedAPIKeys } from "../getInAppWalletSupportedAPIKeys";
|
||||
import { InAppWalletsAPIKeysMenu } from "../inAppWalletsAPIKeysMenu";
|
||||
import { Tabs } from "./_components/tabs";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{
|
||||
clientId: string;
|
||||
}>;
|
||||
searchParams: Promise<{
|
||||
tab?: string;
|
||||
}>;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const authToken = getAuthToken();
|
||||
const { clientId } = await props.params;
|
||||
|
||||
if (!authToken) {
|
||||
redirect(
|
||||
`/login?next=${encodeURIComponent(`/dashboard/connect/in-app-wallets/${clientId}`)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const apiKeys = await getInAppWalletSupportedAPIKeys();
|
||||
const apiKey = apiKeys.find((key) => key.key === clientId);
|
||||
|
||||
if (!apiKey) {
|
||||
redirect("/dashboard/connect/in-app-wallets");
|
||||
}
|
||||
|
||||
const allTimeStatsPromise = getInAppWalletUsage({
|
||||
clientId,
|
||||
from: new Date(2022, 0, 1),
|
||||
to: new Date(),
|
||||
period: "all",
|
||||
});
|
||||
|
||||
const monthlyStatsPromise = getInAppWalletUsage({
|
||||
clientId,
|
||||
from: subDays(new Date(), 30),
|
||||
to: new Date(),
|
||||
period: "month",
|
||||
});
|
||||
|
||||
const [allTimeStats, monthlyStats] = await Promise.all([
|
||||
allTimeStatsPromise,
|
||||
monthlyStatsPromise,
|
||||
]).catch(() => [null, null]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* header */}
|
||||
<div className="flex flex-col gap-4 lg:flex-row lg:justify-between">
|
||||
<PageHeader />
|
||||
<div>
|
||||
<InAppWalletsAPIKeysMenu
|
||||
apiKeys={apiKeys.map((x) => ({
|
||||
name: x.name,
|
||||
key: x.key,
|
||||
}))}
|
||||
selectedAPIKey={apiKey}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="h-8" />
|
||||
|
||||
<InAppWalletsSummary
|
||||
allTimeStats={allTimeStats || []}
|
||||
monthlyStats={monthlyStats || []}
|
||||
/>
|
||||
|
||||
<div className="h-8" />
|
||||
<Tabs clientId={clientId} />
|
||||
<div className="h-8" />
|
||||
{props.children}
|
||||
<div className="h-16" />
|
||||
<InAppWaletFooterSection trackingCategory="embedded-wallet" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
-11
@@ -1,11 +0,0 @@
|
||||
import { redirect } from "next/navigation";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{
|
||||
clientId: string;
|
||||
}>;
|
||||
}) {
|
||||
const { clientId } = await props.params;
|
||||
|
||||
redirect(`/dashboard/connect/in-app-wallets/${clientId}/analytics`);
|
||||
}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
-24
@@ -1,24 +0,0 @@
|
||||
import { InAppWalletUsersPageContent } from "components/embedded-wallets/Users";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getInAppWalletSupportedAPIKeys } from "../../getInAppWalletSupportedAPIKeys";
|
||||
import { TRACKING_CATEGORY } from "../_constants";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{
|
||||
clientId: string;
|
||||
}>;
|
||||
}) {
|
||||
const { clientId } = await props.params;
|
||||
const apiKeys = await getInAppWalletSupportedAPIKeys();
|
||||
const apiKey = apiKeys.find((key) => key.key === clientId);
|
||||
|
||||
if (!apiKey) {
|
||||
redirect("/dashboard/connect/in-app-wallets");
|
||||
}
|
||||
return (
|
||||
<InAppWalletUsersPageContent
|
||||
clientId={apiKey.key}
|
||||
trackingCategory={TRACKING_CATEGORY}
|
||||
/>
|
||||
);
|
||||
}
|
||||
-7
@@ -1,7 +0,0 @@
|
||||
import { getApiKeys } from "../../../../api/lib/getAPIKeys";
|
||||
|
||||
export async function getInAppWalletSupportedAPIKeys() {
|
||||
return (await getApiKeys()).filter((key) => {
|
||||
return !!(key.services || []).find((srv) => srv.name === "embeddedWallets");
|
||||
});
|
||||
}
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import type { ApiKey } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { ApiKeysMenu } from "../../../../../components/settings/ApiKeys/Menu";
|
||||
|
||||
export function InAppWalletsAPIKeysMenu(props: {
|
||||
apiKeys: Pick<ApiKey, "name" | "key">[];
|
||||
selectedAPIKey: Pick<ApiKey, "name" | "key">;
|
||||
}) {
|
||||
const router = useDashboardRouter();
|
||||
return (
|
||||
<ApiKeysMenu
|
||||
apiKeys={props.apiKeys}
|
||||
selectedKey={props.selectedAPIKey}
|
||||
onSelect={(key) => {
|
||||
router.push(`/dashboard/connect/in-app-wallets/${key.key}`);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
@@ -1,33 +0,0 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { NoApiKeys } from "../../../../../components/settings/ApiKeys/NoApiKeys";
|
||||
import { getAuthToken } from "../../../../api/lib/getAuthToken";
|
||||
import { InAppWaletFooterSection } from "../../../../team/[team_slug]/[project_slug]/connect/in-app-wallets/_components/footer";
|
||||
import { PageHeader } from "./PageHeader";
|
||||
import { getInAppWalletSupportedAPIKeys } from "./getInAppWalletSupportedAPIKeys";
|
||||
|
||||
export default async function Page() {
|
||||
const authToken = await getAuthToken();
|
||||
|
||||
if (!authToken) {
|
||||
redirect(
|
||||
`/login?next=${encodeURIComponent("/dashboard/connect/in-app-wallets")}`,
|
||||
);
|
||||
}
|
||||
|
||||
const apiKeys = await getInAppWalletSupportedAPIKeys();
|
||||
const firstKey = apiKeys[0];
|
||||
|
||||
if (firstKey) {
|
||||
redirect(`/dashboard/connect/in-app-wallets/${firstKey.key}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader />
|
||||
<div className="h-8" />
|
||||
<NoApiKeys service="in-app wallets" />
|
||||
<div className="h-16" />
|
||||
<InAppWaletFooterSection trackingCategory="embedded-wallet" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { BillingAlerts } from "../../../../components/settings/Account/Billing/alerts/Alert";
|
||||
import { ConnectSidebarLayout } from "./DashboardConnectLayout";
|
||||
|
||||
export default function Layout(props: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<ConnectSidebarLayout>
|
||||
<BillingAlerts className="pb-6" />
|
||||
{props.children}
|
||||
</ConnectSidebarLayout>
|
||||
);
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { TabPathLinks } from "@/components/ui/tabs";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getApiKeys } from "../../../../../api/lib/getAPIKeys";
|
||||
import { APIKeySelector } from "../components/APIKeySelector";
|
||||
|
||||
export default async function Layout(props: {
|
||||
params: Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
const params = await props.params;
|
||||
const apiKeys = await getApiKeys();
|
||||
const firstKey = apiKeys[0];
|
||||
|
||||
if (!firstKey) {
|
||||
redirect("/dashboard/connect/pay/no-keys");
|
||||
}
|
||||
|
||||
const selectedKey = apiKeys.find((x) => x.id === params.id);
|
||||
|
||||
if (!selectedKey) {
|
||||
redirect("/dashboard/connect/pay");
|
||||
}
|
||||
|
||||
const layoutPath = `/dashboard/connect/pay/${selectedKey.id}`;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="relative flex w-full flex-col gap-4 lg:flex-row">
|
||||
<div className="right-0 bottom-3 z-10 max-sm:w-full lg:absolute lg:min-w-[200px]">
|
||||
<APIKeySelector apiKeys={apiKeys} selectedApiKey={selectedKey} />
|
||||
</div>
|
||||
|
||||
<TabPathLinks
|
||||
className="w-full"
|
||||
links={[
|
||||
{
|
||||
name: "Analytics",
|
||||
path: layoutPath,
|
||||
exactMatch: true,
|
||||
},
|
||||
{
|
||||
name: "Webhooks",
|
||||
path: `${layoutPath}/webhooks`,
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
path: `${layoutPath}/settings`,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="h-4" />
|
||||
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
@@ -1,17 +0,0 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { PayAnalytics } from "../../../../../../components/pay/PayAnalytics/PayAnalytics";
|
||||
import { getAPIKey } from "../../../../../api/lib/getAPIKeys";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
}) {
|
||||
const apiKey = await getAPIKey((await props.params).id);
|
||||
|
||||
if (!apiKey) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return <PayAnalytics clientId={apiKey.key} />;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
@@ -1,17 +0,0 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { PayConfig } from "../../../../../../../components/pay/PayConfig";
|
||||
import { getAPIKey } from "../../../../../../api/lib/getAPIKeys";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
}) {
|
||||
const apiKey = await getAPIKey((await props.params).id);
|
||||
|
||||
if (!apiKey) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return <PayConfig apiKey={apiKey} />;
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
@@ -1,17 +0,0 @@
|
||||
import { notFound } from "next/navigation";
|
||||
import { getAPIKey } from "../../../../../../api/lib/getAPIKeys";
|
||||
import { PayWebhooksPage } from "../../components/webhooks.client";
|
||||
|
||||
export default async function Page(props: {
|
||||
params: Promise<{
|
||||
id: string;
|
||||
}>;
|
||||
}) {
|
||||
const apiKey = await getAPIKey((await props.params).id);
|
||||
|
||||
if (!apiKey) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
return <PayWebhooksPage clientId={apiKey.key} />;
|
||||
}
|
||||
-41
@@ -1,41 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import type { ApiKey } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
|
||||
export function APIKeySelector(props: {
|
||||
apiKeys: ApiKey[];
|
||||
selectedApiKey: ApiKey;
|
||||
}) {
|
||||
const router = useDashboardRouter();
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={props.selectedApiKey.id}
|
||||
onValueChange={(keyId) => {
|
||||
const newKey = props.apiKeys.find((x) => x.id === keyId);
|
||||
if (newKey) {
|
||||
router.push(`/dashboard/connect/pay/${newKey.id}`);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue>{props.selectedApiKey.name}</SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{props.apiKeys.map((key) => (
|
||||
<SelectItem key={key.id} value={key.id}>
|
||||
{key.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
import Link from "next/link";
|
||||
|
||||
export default async function Layout(props: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex w-full flex-col gap-8">
|
||||
<div className="flex flex-col items-start justify-between gap-6 lg:flex-row">
|
||||
<div className="max-w-[800px]">
|
||||
<h1 className="mb-3 font-bold text-3xl tracking-tight lg:text-4xl">
|
||||
Pay
|
||||
</h1>
|
||||
<p className="text-muted-foreground leading-7">
|
||||
Pay allows your users to purchase cryptocurrencies and execute
|
||||
transactions with their credit card or debit card, or with any token
|
||||
via cross-chain routing.{" "}
|
||||
<Link
|
||||
target="_blank"
|
||||
href="https://portal.thirdweb.com/connect/pay/overview"
|
||||
className="!text-link-foreground"
|
||||
>
|
||||
Learn more
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
"use client";
|
||||
|
||||
export { GenericLoadingPage as default } from "@/components/blocks/skeletons/GenericLoadingPage";
|
||||
@@ -1,5 +0,0 @@
|
||||
import { NoApiKeys } from "../../../../../../components/settings/ApiKeys/NoApiKeys";
|
||||
|
||||
export default function Page() {
|
||||
return <NoApiKeys service="Pay in Connect" />;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { getApiKeys } from "../../../../api/lib/getAPIKeys";
|
||||
|
||||
export default async function Page() {
|
||||
const apiKeys = await getApiKeys();
|
||||
const firstKey = apiKeys[0];
|
||||
|
||||
if (firstKey) {
|
||||
redirect(`/dashboard/connect/pay/${firstKey.id}`);
|
||||
}
|
||||
|
||||
redirect("/dashboard/connect/pay/no-keys");
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { getAuthTokenWalletAddress } from "../../../../api/lib/getAuthToken";
|
||||
import { DeployedContractsPage } from "../../../../team/[team_slug]/[project_slug]/contracts/_components/DeployedContractsPage";
|
||||
|
||||
export default async function Page() {
|
||||
const accountAddress = await getAuthTokenWalletAddress();
|
||||
if (!accountAddress) {
|
||||
return redirect(
|
||||
`/login?next=${encodeURIComponent("/dashboard/contracts/deploy")}`,
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<DeployedContractsPage
|
||||
address={accountAddress}
|
||||
className="flex grow flex-col"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { BillingAlerts } from "../../../../components/settings/Account/Billing/alerts/Alert";
|
||||
import { ContractsSidebarLayout } from "../../../../core-ui/sidebar/contracts";
|
||||
|
||||
export default function Layout(props: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<ContractsSidebarLayout>
|
||||
<BillingAlerts className="pb-6" />
|
||||
{props.children}
|
||||
</ContractsSidebarLayout>
|
||||
);
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { Changelog, type ChangelogItem } from "components/dashboard/Changelog";
|
||||
import { HomeProductCard } from "components/dashboard/HomeProductCard";
|
||||
import { OnboardingSteps } from "components/onboarding/Steps";
|
||||
import { PRODUCTS } from "components/product-pages/common/nav/data";
|
||||
|
||||
const TRACKING_CATEGORY = "dashboard";
|
||||
|
||||
export default async function Page() {
|
||||
const changelog = await getChangelog();
|
||||
|
||||
return (
|
||||
<div className="container flex flex-col justify-between gap-16 pt-8 pb-16 xl:flex-row">
|
||||
<div className="grow">
|
||||
<h1 className="mb-6 font-semibold text-2xl tracking-tight lg:mb-8 lg:text-3xl">
|
||||
Get started quickly
|
||||
</h1>
|
||||
<div className="flex w-full flex-col gap-10">
|
||||
<OnboardingSteps />
|
||||
<div className="flex w-full flex-col gap-12">
|
||||
{["connect", "contracts", "infrastructure"].map((section) => {
|
||||
const products = PRODUCTS.filter(
|
||||
(p) => p.section === section && !!p.dashboardLink,
|
||||
);
|
||||
|
||||
return (
|
||||
<div key={section}>
|
||||
<h3 className="mb-2.5 font-medium text-muted-foreground text-xl capitalize tracking-tight">
|
||||
{section === "infrastructure" ? "Engine" : section}
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 gap-4 lg:grid-cols-3">
|
||||
{products.map((product) => (
|
||||
<HomeProductCard
|
||||
key={product.name}
|
||||
product={product}
|
||||
TRACKING_CATEGORY={TRACKING_CATEGORY}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="shrink-0 lg:w-[320px]">
|
||||
<h2 className="mb-4 font-semibold text-lg tracking-tight">
|
||||
Latest changes
|
||||
</h2>
|
||||
<Changelog changelog={changelog} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function getChangelog() {
|
||||
const res = await fetch(
|
||||
"https://thirdweb.ghost.io/ghost/api/content/posts/?key=49c62b5137df1c17ab6b9e46e3&fields=title,url,published_at&filter=tag:changelog&visibility:public&limit=5",
|
||||
);
|
||||
const json = await res.json();
|
||||
return json.posts as ChangelogItem[];
|
||||
}
|
||||
|
||||
// revalidate every 5 minutes
|
||||
export const revalidate = 300;
|
||||
@@ -1,9 +1,3 @@
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
} from "@/components/ui/breadcrumb";
|
||||
import { ContractRow } from "components/explore/contract-row";
|
||||
import { DeployUpsellCard } from "components/explore/upsells/deploy-your-own";
|
||||
import { PublishUpsellCard } from "components/explore/upsells/publish-submit";
|
||||
@@ -23,13 +17,6 @@ export const metadata: Metadata = {
|
||||
export default async function ExplorePage() {
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<Breadcrumb className="border-border border-b px-6 py-4">
|
||||
<BreadcrumbList>
|
||||
<BreadcrumbItem>
|
||||
<BreadcrumbPage>Explore</BreadcrumbPage>
|
||||
</BreadcrumbItem>
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
<div className="container flex flex-col gap-4 py-8">
|
||||
<h1 className="mb-3 font-bold text-3xl tracking-tighter lg:text-5xl">
|
||||
Explore
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { AppFooter } from "@/components/blocks/app-footer";
|
||||
import { ErrorProvider } from "../../contexts/error-handler";
|
||||
import { DashboardHeader } from "../components/Header/DashboardHeader";
|
||||
import { TeamHeader } from "../team/components/TeamHeader/team-header";
|
||||
|
||||
export default function DashboardLayout(props: { children: React.ReactNode }) {
|
||||
export default function DashboardLayout(props: {
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<ErrorProvider>
|
||||
<div className="flex min-h-screen flex-col bg-background">
|
||||
<DashboardHeader />
|
||||
<div className="border-border border-b bg-muted/50">
|
||||
<TeamHeader />
|
||||
</div>
|
||||
<div className="flex grow flex-col">{props.children}</div>
|
||||
<AppFooter />
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ export default async function Page(props: {
|
||||
const searchParams = await props.searchParams;
|
||||
if (!searchParams?.uri) {
|
||||
// redirect back out if we do not have a uri
|
||||
return redirect("/dashboard");
|
||||
return redirect("/team");
|
||||
}
|
||||
|
||||
const contractMetadata = await fetchDeployMetadata({
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { type SortBy, type TimeRange, fetchTopContracts } from "lib/search";
|
||||
import type { Metadata } from "next";
|
||||
import { ContractsSidebarLayout } from "../../../core-ui/sidebar/contracts";
|
||||
import { TrendingContractSection } from "./components/trending-table";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -26,7 +25,7 @@ export default async function DashboardContractTrendingPage(props: {
|
||||
});
|
||||
|
||||
return (
|
||||
<ContractsSidebarLayout>
|
||||
<div className="container py-8">
|
||||
<h1 className="mb-5 font-semibold text-2xl tracking-tight md:text-3xl">
|
||||
Trending Contracts
|
||||
</h1>
|
||||
@@ -35,6 +34,6 @@ export default async function DashboardContractTrendingPage(props: {
|
||||
searchParams={await props.searchParams}
|
||||
showPagination={true}
|
||||
/>
|
||||
</ContractsSidebarLayout>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
|
||||
import type { Project } from "@/api/projects";
|
||||
import type { Team } from "@/api/team";
|
||||
import { useThirdwebClient } from "@/constants/thirdweb.client";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet";
|
||||
import { useAccount } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { useCallback, useState } from "react";
|
||||
import { useActiveWallet, useDisconnect } from "thirdweb/react";
|
||||
import { LazyCreateAPIKeyDialog } from "../../../components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog";
|
||||
import {
|
||||
type AccountHeaderCompProps,
|
||||
@@ -20,6 +22,9 @@ export function AccountHeader(props: {
|
||||
const router = useDashboardRouter();
|
||||
const [isCreateProjectDialogOpen, setIsCreateProjectDialogOpen] =
|
||||
useState(false);
|
||||
const client = useThirdwebClient();
|
||||
const wallet = useActiveWallet();
|
||||
const { disconnect } = useDisconnect();
|
||||
|
||||
const logout = useCallback(async () => {
|
||||
// log out the user
|
||||
@@ -27,11 +32,14 @@ export function AccountHeader(props: {
|
||||
await fetch("/api/auth/logout", {
|
||||
method: "POST",
|
||||
});
|
||||
router.push("/login");
|
||||
if (wallet) {
|
||||
disconnect(wallet);
|
||||
}
|
||||
router.refresh();
|
||||
} catch (e) {
|
||||
console.error("Failed to log out", e);
|
||||
}
|
||||
}, [router]);
|
||||
}, [router, disconnect, wallet]);
|
||||
|
||||
const headerProps: AccountHeaderCompProps = {
|
||||
teamsAndProjects: props.teamsAndProjects,
|
||||
@@ -39,6 +47,7 @@ export function AccountHeader(props: {
|
||||
connectButton: <CustomConnectWallet />,
|
||||
createProject: () => setIsCreateProjectDialogOpen(true),
|
||||
account: myAccountQuery.data,
|
||||
client,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -49,7 +58,6 @@ export function AccountHeader(props: {
|
||||
<LazyCreateAPIKeyDialog
|
||||
open={isCreateProjectDialogOpen}
|
||||
onOpenChange={setIsCreateProjectDialogOpen}
|
||||
wording="project"
|
||||
onCreateAndComplete={() => {
|
||||
// refresh projects
|
||||
router.refresh();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { getThirdwebClient } from "@/constants/thirdweb.server";
|
||||
import type { Meta, StoryObj } from "@storybook/react";
|
||||
import { ThirdwebProvider } from "thirdweb/react";
|
||||
import { teamsAndProjectsStub } from "../../../stories/stubs";
|
||||
@@ -36,6 +37,8 @@ export const Mobile: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
const client = getThirdwebClient();
|
||||
|
||||
function Variants(props: {
|
||||
type: "mobile" | "desktop";
|
||||
}) {
|
||||
@@ -55,6 +58,7 @@ function Variants(props: {
|
||||
id: "foo",
|
||||
email: "[email protected]",
|
||||
}}
|
||||
client={client}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
|
||||
@@ -65,6 +69,7 @@ function Variants(props: {
|
||||
connectButton={<ConnectButtonStub />}
|
||||
createProject={() => {}}
|
||||
account={undefined}
|
||||
client={client}
|
||||
/>
|
||||
</BadgeContainer>
|
||||
</div>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { GradientAvatar } from "@/components/blocks/Avatars/GradientAvatar";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import Link from "next/link";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import { SecondaryNav } from "../../components/Header/SecondaryNav/SecondaryNav";
|
||||
import { MobileBurgerMenuButton } from "../../components/MobileBurgerMenuButton";
|
||||
import { ThirdwebMiniLogo } from "../../components/ThirdwebMiniLogo";
|
||||
@@ -17,6 +18,7 @@ export type AccountHeaderCompProps = {
|
||||
teamsAndProjects: Array<{ team: Team; projects: Project[] }>;
|
||||
createProject: () => void;
|
||||
account: Pick<Account, "email" | "id"> | undefined;
|
||||
client: ThirdwebClient;
|
||||
};
|
||||
|
||||
export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) {
|
||||
@@ -41,21 +43,24 @@ export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) {
|
||||
>
|
||||
{/* TODO - set account Image */}
|
||||
<GradientAvatar
|
||||
id={props.account?.id}
|
||||
src={props.account ? "" : undefined}
|
||||
id={props.account?.id || "default"}
|
||||
src={""}
|
||||
className="size-6"
|
||||
client={props.client}
|
||||
/>
|
||||
<span> My Account </span>
|
||||
</Link>
|
||||
|
||||
<TeamAndProjectSelectorPopoverButton
|
||||
currentProject={undefined}
|
||||
currentTeam={undefined}
|
||||
teamsAndProjects={props.teamsAndProjects}
|
||||
focus="team-selection"
|
||||
createProject={props.createProject}
|
||||
account={props.account}
|
||||
/>
|
||||
{props.teamsAndProjects.length > 0 && (
|
||||
<TeamAndProjectSelectorPopoverButton
|
||||
currentProject={undefined}
|
||||
currentTeam={undefined}
|
||||
teamsAndProjects={props.teamsAndProjects}
|
||||
focus="team-selection"
|
||||
createProject={props.createProject}
|
||||
account={props.account}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -63,6 +68,7 @@ export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) {
|
||||
account={props.account}
|
||||
logout={props.logout}
|
||||
connectButton={props.connectButton}
|
||||
client={props.client}
|
||||
/>
|
||||
</header>
|
||||
);
|
||||
@@ -89,16 +95,19 @@ export function AccountHeaderMobileUI(props: AccountHeaderCompProps) {
|
||||
id={props.account?.id}
|
||||
src={props.account ? "" : undefined}
|
||||
className="size-6"
|
||||
client={props.client}
|
||||
/>
|
||||
<span> My Account </span>
|
||||
</Link>
|
||||
|
||||
<TeamSelectorMobileMenuButton
|
||||
currentTeam={undefined}
|
||||
teamsAndProjects={props.teamsAndProjects}
|
||||
upgradeTeamLink={undefined}
|
||||
account={props.account}
|
||||
/>
|
||||
{props.teamsAndProjects.length > 0 && (
|
||||
<TeamSelectorMobileMenuButton
|
||||
currentTeam={undefined}
|
||||
teamsAndProjects={props.teamsAndProjects}
|
||||
upgradeTeamLink={undefined}
|
||||
account={props.account}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import type { Project } from "@/api/projects";
|
||||
import type { Team } from "@/api/team";
|
||||
import { useAccount } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { AccountHeader } from "./AccountHeader";
|
||||
|
||||
// Remove this file when AppShell is deleted
|
||||
|
||||
export function AccountHeaderClient() {
|
||||
const accountQuery = useAccount();
|
||||
const teamsQuery = useQuery({
|
||||
queryKey: ["teams", accountQuery.data?.id],
|
||||
queryFn: () => getTeamsClient(),
|
||||
});
|
||||
|
||||
const teamsAndProjectsQuery = useQuery({
|
||||
queryKey: [teamsQuery.data?.[0]?.slug, "projects"],
|
||||
queryFn: async () => {
|
||||
const teams = teamsQuery.data;
|
||||
if (!teams) {
|
||||
throw new Error("No teams");
|
||||
}
|
||||
|
||||
const teamsAndProjects = await Promise.all(
|
||||
teams.map(async (team) => ({
|
||||
team,
|
||||
projects: await getProjectsForTeamClient(team.slug),
|
||||
})),
|
||||
);
|
||||
|
||||
return teamsAndProjects;
|
||||
},
|
||||
enabled: !!teamsQuery.data,
|
||||
});
|
||||
|
||||
return <AccountHeader teamsAndProjects={teamsAndProjectsQuery.data || []} />;
|
||||
}
|
||||
|
||||
async function getTeamsClient() {
|
||||
const res = await fetch("/api/server-proxy/api/v1/teams");
|
||||
if (res.ok) {
|
||||
return (await res.json()).result as Team[];
|
||||
}
|
||||
throw new Error("Failed to fetch teams");
|
||||
}
|
||||
|
||||
async function getProjectsForTeamClient(teamSlug: string) {
|
||||
const res = await fetch(
|
||||
`/api/server-proxy/api/v1/teams/${teamSlug}/projects`,
|
||||
);
|
||||
if (res.ok) {
|
||||
return (await res.json()).result as Project[];
|
||||
}
|
||||
throw new Error("Failed to fetch projects");
|
||||
}
|
||||
+1
-1
@@ -1,10 +1,10 @@
|
||||
"use client";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { ImportModal } from "components/contract-components/import-contract/modal";
|
||||
import { DownloadIcon, PlusIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { ImportModal } from "../../../../../components/contract-components/import-contract/modal";
|
||||
|
||||
export function DeployedContractsPageHeader() {
|
||||
const [importModalOpen, setImportModalOpen] = useState(false);
|
||||
+3
-1
@@ -1,6 +1,7 @@
|
||||
import { Spinner } from "@/components/ui/Spinner/Spinner";
|
||||
import { ClientOnly } from "components/ClientOnly/ClientOnly";
|
||||
import { BillingAlerts } from "components/settings/Account/Billing/alerts/Alert";
|
||||
import { Suspense } from "react";
|
||||
import { ClientOnly } from "../../../../../../components/ClientOnly/ClientOnly";
|
||||
import { DeployedContractsPageHeader } from "../DeployedContractsPageHeader";
|
||||
import { DeployedContractsTable } from "./DeployedContractsTable";
|
||||
import { GetStartedWithContractsDeploy } from "./GetStartedWithContractsDeploy";
|
||||
@@ -12,6 +13,7 @@ export function DeployedContractsPage(props: {
|
||||
}) {
|
||||
return (
|
||||
<div className={props.className}>
|
||||
<BillingAlerts className="mb-10" />
|
||||
<DeployedContractsPageHeader />
|
||||
<div className="h-6" />
|
||||
<Suspense fallback={<Loading />}>
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import { DeployedContracts } from "components/contract-components/tables/deployed-contracts";
|
||||
import type { BasicContract } from "contract-ui/types/types";
|
||||
import { DeployedContracts } from "../../../../../../components/contract-components/tables/deployed-contracts";
|
||||
|
||||
export function DeployedContractsTable(props: {
|
||||
contracts: BasicContract[];
|
||||
+3
-3
@@ -1,11 +1,11 @@
|
||||
"use client";
|
||||
import { TabButtons } from "@/components/ui/tabs";
|
||||
import { useDashboardRouter } from "@/lib/DashboardRouter";
|
||||
import { ImportModal } from "components/contract-components/import-contract/modal";
|
||||
import { StepsCard } from "components/dashboard/StepsCard";
|
||||
import { useTrack } from "hooks/analytics/useTrack";
|
||||
import Image from "next/image";
|
||||
import { useMemo, useState } from "react";
|
||||
import { ImportModal } from "../../../../../../components/contract-components/import-contract/modal";
|
||||
import { StepsCard } from "../../../../../../components/dashboard/StepsCard";
|
||||
import { useTrack } from "../../../../../../hooks/analytics/useTrack";
|
||||
|
||||
export function GetStartedWithContractsDeploy() {
|
||||
const steps = useMemo(
|
||||
+3
-3
@@ -1,7 +1,7 @@
|
||||
import { MULTICHAIN_REGISTRY_CONTRACT } from "constants/contracts";
|
||||
import type { BasicContract } from "contract-ui/types/types";
|
||||
import { MULTICHAIN_REGISTRY_CONTRACT } from "../../../../../../constants/contracts";
|
||||
import { getAllMultichainRegistry } from "../../../../../../dashboard-extensions/common/read/getAllMultichainRegistry";
|
||||
import { fetchChain } from "../../../../../../utils/fetchChain";
|
||||
import { getAllMultichainRegistry } from "../../../../dashboard-extensions/common/read/getAllMultichainRegistry";
|
||||
import { fetchChain } from "../../../../utils/fetchChain";
|
||||
|
||||
export async function getSortedDeployedContracts(params: {
|
||||
address: string;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { getAuthTokenWalletAddress } from "../../api/lib/getAuthToken";
|
||||
import { DeployedContractsPage } from "./_components/DeployedContractsPage";
|
||||
|
||||
export default async function Page() {
|
||||
const accountAddress = await getAuthTokenWalletAddress();
|
||||
|
||||
if (!accountAddress) {
|
||||
return redirect(`/login?next=${encodeURIComponent("/account/contracts")}`);
|
||||
}
|
||||
|
||||
return (
|
||||
<DeployedContractsPage
|
||||
address={accountAddress}
|
||||
className="flex grow flex-col pt-10 pb-10"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
"use client";
|
||||
|
||||
import { ChakraProviderSetup } from "@/components/ChakraProviderSetup";
|
||||
import { useAuthorizedWallets } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { BillingAlerts } from "components/settings/Account/Billing/alerts/Alert";
|
||||
import { AuthorizedWalletsTable } from "components/settings/AuthorizedWallets/AuthorizedWalletsTable";
|
||||
|
||||
// TODO - remove ChakraProviderSetup after migrating AuthorizedWalletsTable
|
||||
// TODO - fetch the authorized wallets server side
|
||||
|
||||
export default function Page() {
|
||||
const authorizedWalletsQuery = useAuthorizedWallets();
|
||||
|
||||
return (
|
||||
<div className="container py-8">
|
||||
<BillingAlerts className="mb-10" />
|
||||
<div className="flex flex-col gap-8">
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col justify-between gap-4">
|
||||
<h1 className="font-semibold text-3xl tracking-tight">Devices</h1>
|
||||
<p className="text-muted-foreground">
|
||||
List of authorized devices that can perform actions on behalf of
|
||||
your account.
|
||||
</p>
|
||||
<ChakraProviderSetup>
|
||||
<AuthorizedWalletsTable
|
||||
authorizedWallets={authorizedWalletsQuery.data || []}
|
||||
isPending={authorizedWalletsQuery.isPending}
|
||||
isFetched={authorizedWalletsQuery.isFetched}
|
||||
/>
|
||||
</ChakraProviderSetup>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,8 @@
|
||||
import { getProjects } from "@/api/projects";
|
||||
import { getTeams } from "@/api/team";
|
||||
import { DashboardTypeCookieSetter } from "@/components/DashboardTypeCookieSetter";
|
||||
import { SidebarLayout } from "@/components/blocks/SidebarLayout";
|
||||
import { AppFooter } from "@/components/blocks/app-footer";
|
||||
import type React from "react";
|
||||
import { TabPathLinks } from "../../@/components/ui/tabs";
|
||||
import { TWAutoConnect } from "../components/autoconnect";
|
||||
import { AccountHeader } from "./components/AccountHeader";
|
||||
|
||||
@@ -14,36 +13,9 @@ export default async function AccountLayout(props: {
|
||||
<div className="flex min-h-screen flex-col bg-background">
|
||||
<div className="flex grow flex-col">
|
||||
<HeaderAndNav />
|
||||
<div className="border-border border-b py-10">
|
||||
<div className="mx-auto max-w-[1100px] px-6">
|
||||
<h1 className="font-semibold text-3xl tracking-tight">
|
||||
My Account
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
<SidebarLayout
|
||||
className="max-w-[1100px]"
|
||||
sidebarLinks={[
|
||||
{
|
||||
href: "/account",
|
||||
label: "Overview",
|
||||
exactMatch: true,
|
||||
},
|
||||
{
|
||||
href: "/account/wallets",
|
||||
label: "Wallets",
|
||||
},
|
||||
{
|
||||
href: "/account/settings",
|
||||
label: "Settings",
|
||||
},
|
||||
]}
|
||||
>
|
||||
{props.children}
|
||||
</SidebarLayout>
|
||||
{props.children}
|
||||
</div>
|
||||
<TWAutoConnect />
|
||||
<DashboardTypeCookieSetter type="team" />
|
||||
<AppFooter />
|
||||
</div>
|
||||
);
|
||||
@@ -60,8 +32,36 @@ async function HeaderAndNav() {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="border-b bg-muted/50">
|
||||
<div className="bg-muted/50">
|
||||
<AccountHeader teamsAndProjects={teamsAndProjects} />
|
||||
<TabPathLinks
|
||||
tabContainerClassName="px-4 lg:px-6"
|
||||
links={[
|
||||
{
|
||||
path: "/account",
|
||||
name: "Overview",
|
||||
exactMatch: true,
|
||||
},
|
||||
{
|
||||
path: "/account/contracts",
|
||||
name: "Contracts",
|
||||
exactMatch: true,
|
||||
},
|
||||
// TODO - enable these links after they are functional
|
||||
// {
|
||||
// path: "/account/settings",
|
||||
// name: "Settings",
|
||||
// },
|
||||
// {
|
||||
// path: "/account/wallets",
|
||||
// name: "Wallets",
|
||||
// },
|
||||
{
|
||||
path: "/account/devices",
|
||||
name: "Devices",
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import { useThirdwebClient } from "@/constants/thirdweb.client";
|
||||
import { EllipsisIcon, PlusIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
@@ -97,14 +98,19 @@ function TeamRow(props: {
|
||||
team: Team;
|
||||
role: TeamAccountRole;
|
||||
}) {
|
||||
const client = useThirdwebClient();
|
||||
const plan = getValidTeamPlan(props.team);
|
||||
|
||||
return (
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
{/* start */}
|
||||
<div className="flex items-center gap-4">
|
||||
{/* TODO - set image */}
|
||||
<GradientAvatar className="size-8" src={""} id={props.team.id} />
|
||||
<GradientAvatar
|
||||
className="size-8"
|
||||
src={props.team.image}
|
||||
id={props.team.id}
|
||||
client={client}
|
||||
/>
|
||||
|
||||
<div>
|
||||
<div className="flex items-center gap-3">
|
||||
|
||||
@@ -1,17 +1,45 @@
|
||||
import { getTeams } from "@/api/team";
|
||||
import { getMembers } from "@/api/team-members";
|
||||
import { BillingAlerts } from "components/settings/Account/Billing/alerts/Alert";
|
||||
import { redirect } from "next/navigation";
|
||||
import { AccountTeamsUI } from "./overview/AccountTeamsUI";
|
||||
import { getAccount } from "./settings/getAccount";
|
||||
|
||||
export default async function Page() {
|
||||
const account = await getAccount();
|
||||
|
||||
if (!account) {
|
||||
redirect("/login?next=/account");
|
||||
}
|
||||
|
||||
const teams = await getTeams();
|
||||
|
||||
const teamsWithRole = teams.map((team) => ({
|
||||
team,
|
||||
// TODO fetch the role of current user in this team
|
||||
role: "MEMBER" as const, // THIS IS A PLACEHOLDER !!!
|
||||
}));
|
||||
const teamsWithRole = (
|
||||
await Promise.all(
|
||||
teams.map(async (team) => {
|
||||
const members = await getMembers(team.slug);
|
||||
if (!members) {
|
||||
return {
|
||||
team,
|
||||
role: "MEMBER" as const,
|
||||
};
|
||||
}
|
||||
|
||||
const accountMemberInfo = members.find(
|
||||
(m) => m.accountId === account.id,
|
||||
);
|
||||
|
||||
return {
|
||||
team,
|
||||
role: accountMemberInfo?.role || "MEMBER",
|
||||
};
|
||||
}),
|
||||
)
|
||||
).filter((x) => !!x);
|
||||
|
||||
return (
|
||||
<div className="grow">
|
||||
<div className="container grow py-8">
|
||||
<BillingAlerts className="mb-10" />
|
||||
<AccountTeamsUI teamsWithRole={teamsWithRole} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { BillingAlerts } from "components/settings/Account/Billing/alerts/Alert";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import { upload } from "thirdweb/storage";
|
||||
import { AccountSettingsPageUI } from "./AccountSettingsPageUI";
|
||||
@@ -10,24 +11,27 @@ export function AccountSettingsPage(props: {
|
||||
client: ThirdwebClient;
|
||||
}) {
|
||||
return (
|
||||
<AccountSettingsPageUI
|
||||
account={props.account}
|
||||
updateAccountImage={async (file) => {
|
||||
if (file) {
|
||||
// upload to IPFS
|
||||
const ipfsUri = await upload({
|
||||
client: props.client,
|
||||
files: [file],
|
||||
});
|
||||
<div>
|
||||
<BillingAlerts className="mb-10" />
|
||||
<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
|
||||
}
|
||||
// TODO - Implement updating the account image with uri
|
||||
console.log(ipfsUri);
|
||||
} else {
|
||||
// TODO - Implement deleting the account image
|
||||
}
|
||||
|
||||
throw new Error("Not implemented");
|
||||
}}
|
||||
/>
|
||||
throw new Error("Not implemented");
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { BillingAlerts } from "components/settings/Account/Billing/alerts/Alert";
|
||||
import { LinkWalletUI } from "./LinkWalletUI";
|
||||
|
||||
export default async function Page() {
|
||||
return (
|
||||
<div className="grow">
|
||||
<BillingAlerts className="mb-10" />
|
||||
<LinkWalletUI
|
||||
// TODO - fetch the wallets of current user
|
||||
wallets={[]}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { getAuthToken } from "./getAuthToken";
|
||||
|
||||
// TODO - Fix the `/v1/keys/${apiKeyId}` endpoint in API server
|
||||
|
||||
export async function getAPIKey(apiKeyId: string) {
|
||||
async function getAPIKey(apiKeyId: string) {
|
||||
// The `/v1/keys/${apiKeyId}`; does not return the "FULL" ApiKey object for some reason
|
||||
// Until this is fixed in API server - we just use the getApiKeys() and find the key by id
|
||||
|
||||
@@ -35,7 +35,7 @@ export async function getAPIKey(apiKeyId: string) {
|
||||
// return json.data as ApiKey;
|
||||
}
|
||||
|
||||
export async function getApiKeys() {
|
||||
async function getApiKeys() {
|
||||
const authToken = await getAuthToken();
|
||||
|
||||
const res = await fetch(`${API_SERVER_URL}/v1/keys`, {
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
import { NavLink } from "@/components/ui/NavLink";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet";
|
||||
import { CmdKSearch } from "components/cmd-k-search";
|
||||
import { ColorModeToggle } from "components/color-mode/color-mode-toggle";
|
||||
import { Logo } from "components/logo";
|
||||
import { CreditsButton } from "components/settings/Account/Billing/CreditsButton";
|
||||
import { UpgradeButton } from "components/settings/Account/Billing/UpgradeButton";
|
||||
import { CircleHelpIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import { Suspense } from "react";
|
||||
import { DashboardHeaderTabs } from "./DashboardHeaderTabs";
|
||||
|
||||
export const DashboardHeader: React.FC = () => {
|
||||
return (
|
||||
<div className="bg-muted/50">
|
||||
<header className="flex items-center justify-between px-4 py-3 lg:px-6">
|
||||
{/* left */}
|
||||
<div className="flex items-center gap-4">
|
||||
<Link href="/dashboard">
|
||||
<Logo hideWordmark />
|
||||
</Link>
|
||||
<CmdKSearch />
|
||||
</div>
|
||||
|
||||
{/* Right */}
|
||||
<div className="flex items-center gap-2">
|
||||
<NavLink
|
||||
href="/team"
|
||||
tracking={{
|
||||
category: "header",
|
||||
action: "click",
|
||||
label: "team-dashboard",
|
||||
}}
|
||||
className="hidden items-center gap-1.5 px-2.5 text-muted-foreground text-sm hover:text-foreground md:inline-flex"
|
||||
activeClassName="text-foreground"
|
||||
>
|
||||
Team Dashboard <Badge className="px-1.5"> BETA </Badge>
|
||||
</NavLink>
|
||||
|
||||
<div className="hidden gap-2 md:flex">
|
||||
<Suspense fallback={null}>
|
||||
<CreditsButton />
|
||||
</Suspense>
|
||||
<UpgradeButton />
|
||||
</div>
|
||||
|
||||
<HeaderNavLink
|
||||
trackingLabel="chainlist"
|
||||
href="/chainlist"
|
||||
label="Chainlist"
|
||||
/>
|
||||
|
||||
<HeaderNavLink
|
||||
trackingLabel="docs"
|
||||
href="https://portal.thirdweb.com"
|
||||
label="Docs"
|
||||
/>
|
||||
|
||||
<HeaderNavLink
|
||||
trackingLabel="support"
|
||||
href="/support"
|
||||
label="Support"
|
||||
/>
|
||||
|
||||
<div className="md:hidden">
|
||||
<Button asChild variant="ghost">
|
||||
<NavLink
|
||||
className="fade-in-0 !h-auto !w-auto p-2"
|
||||
href="/support"
|
||||
tracking={{
|
||||
category: "header",
|
||||
action: "click",
|
||||
label: "support",
|
||||
}}
|
||||
>
|
||||
<CircleHelpIcon className="size-5" />
|
||||
</NavLink>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<ColorModeToggle />
|
||||
|
||||
<div className="md:ml-2">
|
||||
<CustomConnectWallet />
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<DashboardHeaderTabs />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function HeaderNavLink(props: {
|
||||
label: string;
|
||||
trackingLabel: string;
|
||||
href: string;
|
||||
}) {
|
||||
return (
|
||||
<NavLink
|
||||
href={props.href}
|
||||
tracking={{
|
||||
category: "header",
|
||||
action: "click",
|
||||
label: props.trackingLabel,
|
||||
}}
|
||||
className="hidden px-2.5 text-muted-foreground text-sm hover:text-foreground md:block"
|
||||
activeClassName="text-foreground"
|
||||
>
|
||||
{props.label}
|
||||
</NavLink>
|
||||
);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { TabLinks } from "@/components/ui/tabs";
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
export function DashboardHeaderTabs() {
|
||||
const pathname = usePathname() || "";
|
||||
return (
|
||||
<TabLinks
|
||||
className="w-full"
|
||||
tabContainerClassName="px-4"
|
||||
shadowColor="transparent"
|
||||
links={[
|
||||
{
|
||||
href: "/dashboard",
|
||||
name: "Home",
|
||||
isActive: pathname === "/dashboard",
|
||||
},
|
||||
{
|
||||
href: "/dashboard/connect/analytics",
|
||||
name: "Connect",
|
||||
isActive: pathname.startsWith("/dashboard/connect"),
|
||||
},
|
||||
{
|
||||
href: "/dashboard/contracts/deploy",
|
||||
name: "Contracts",
|
||||
isActive: pathname.startsWith("/dashboard/contracts"),
|
||||
},
|
||||
{
|
||||
href: "/dashboard/engine",
|
||||
name: "Engine",
|
||||
isActive: pathname.startsWith("/dashboard/engine"),
|
||||
},
|
||||
{
|
||||
href: "/dashboard/settings/api-keys",
|
||||
name: "Settings",
|
||||
isActive: pathname.startsWith("/dashboard/settings"),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -55,7 +55,7 @@ export function ResourcesDropdownButton() {
|
||||
|
||||
<DropdownMenuItem>
|
||||
<Link href="/trending" className="w-full p-1">
|
||||
Popular Contracts
|
||||
Trending Contracts
|
||||
</Link>
|
||||
</DropdownMenuItem>
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { ToolTipLabel } from "@/components/ui/tooltip";
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { ArrowUpDownIcon } from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import type React from "react";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
import { ResourcesDropdownButton } from "./ResourcesDropdownButton";
|
||||
import { AccountButton } from "./account-button.client";
|
||||
|
||||
@@ -10,6 +9,7 @@ export function SecondaryNav(props: {
|
||||
account: Pick<Account, "email" | "id"> | undefined;
|
||||
logout: () => void;
|
||||
connectButton: React.ReactNode;
|
||||
client: ThirdwebClient;
|
||||
}) {
|
||||
return (
|
||||
<div className="flex items-center gap-6">
|
||||
@@ -18,6 +18,7 @@ export function SecondaryNav(props: {
|
||||
logout={props.logout}
|
||||
connectButton={props.connectButton}
|
||||
account={props.account}
|
||||
client={props.client}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -26,23 +27,6 @@ export function SecondaryNav(props: {
|
||||
export function SecondaryNavLinks() {
|
||||
return (
|
||||
<div className="flex items-center gap-6">
|
||||
<ToolTipLabel
|
||||
label={
|
||||
<span>
|
||||
You are exploring the new dashboard. <br /> Click here to go back to
|
||||
the legacy dashboard.
|
||||
</span>
|
||||
}
|
||||
>
|
||||
<Link
|
||||
href="/dashboard"
|
||||
className="inline-flex items-center gap-1.5 text-muted-foreground text-sm hover:text-foreground"
|
||||
>
|
||||
<ArrowUpDownIcon className="size-4" />
|
||||
Switch To Legacy Dashboard
|
||||
</Link>
|
||||
</ToolTipLabel>
|
||||
|
||||
<ResourcesDropdownButton />
|
||||
|
||||
<Link
|
||||
|
||||
@@ -7,105 +7,119 @@ import {
|
||||
DropdownMenuContent,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { SkeletonContainer } from "@/components/ui/skeleton";
|
||||
import { cn } from "@/lib/utils";
|
||||
import type { Account } from "@3rdweb-sdk/react/hooks/useApi";
|
||||
import { LogOutIcon, Moon, Sun } from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import type { ThirdwebClient } from "thirdweb";
|
||||
|
||||
export function AccountButton(props: {
|
||||
logout: () => void;
|
||||
connectButton: React.ReactNode;
|
||||
account?: Pick<Account, "email" | "id">;
|
||||
client: ThirdwebClient;
|
||||
}) {
|
||||
const { setTheme, theme } = useTheme();
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
<DropdownMenu open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
size="icon"
|
||||
asChild
|
||||
className="size-10 rounded-full hover:ring-2 hover:ring-offset-2"
|
||||
className="size-10 cursor-pointer rounded-full hover:ring-2 hover:ring-offset-2"
|
||||
variant="ghost"
|
||||
>
|
||||
<button type="button">
|
||||
{/* Don't remove the div */}
|
||||
<div>
|
||||
{/* TODO - set account image */}
|
||||
<GradientAvatar
|
||||
id={props.account?.id}
|
||||
id={props.account?.id || "default"}
|
||||
src={""}
|
||||
className="size-9"
|
||||
client={props.client}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-[300px] rounded-lg p-0" align="end">
|
||||
<DropdownMenuContent
|
||||
className="w-[300px] rounded-lg p-0"
|
||||
align="end"
|
||||
onClick={(e) => {
|
||||
if (e.target instanceof HTMLAnchorElement) {
|
||||
setIsOpen(false);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="border-border border-b p-4 pb-5">
|
||||
<SkeletonContainer
|
||||
skeletonData="[email protected]"
|
||||
loadedData={props.account?.email}
|
||||
render={(v) => <p className="text-muted-foreground text-sm">{v}</p>}
|
||||
/>
|
||||
{props.account?.email && (
|
||||
<p className="text-muted-foreground text-sm">
|
||||
{props.account.email}
|
||||
</p>
|
||||
)}
|
||||
|
||||
<div className="h-3" />
|
||||
<div className="[&>*]:!w-full">{props.connectButton}</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col gap-4 px-4 py-5">
|
||||
<Link
|
||||
href="/account"
|
||||
className="text-muted-foreground text-sm hover:text-foreground"
|
||||
<div className="flex flex-col gap-1 p-2">
|
||||
<Button
|
||||
asChild
|
||||
variant="ghost"
|
||||
className="justify-start px-3 text-muted-foreground text-sm hover:text-foreground"
|
||||
>
|
||||
Account Settings
|
||||
</Link>
|
||||
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<p className="text-muted-foreground text-sm">Theme</p>
|
||||
|
||||
<div className="ml-auto flex items-center gap-1 rounded-lg border border-border px-2 py-0.5">
|
||||
<Button
|
||||
size="icon"
|
||||
onClick={() => setTheme("light")}
|
||||
variant="ghost"
|
||||
aria-label="Light theme"
|
||||
className={cn(
|
||||
"!p-1 !h-auto !w-auto",
|
||||
theme === "light" ? "opacity-100" : "opacity-30",
|
||||
)}
|
||||
>
|
||||
<Sun className="size-4" />
|
||||
</Button>
|
||||
<Button
|
||||
size="icon"
|
||||
onClick={() => setTheme("dark")}
|
||||
variant="ghost"
|
||||
aria-label="Dark theme"
|
||||
className={cn(
|
||||
"!p-1 !h-auto !w-auto",
|
||||
theme === "dark" ? "opacity-100" : "opacity-30",
|
||||
)}
|
||||
>
|
||||
<Moon className="size-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Link
|
||||
href="/"
|
||||
className="text-muted-foreground text-sm hover:text-foreground"
|
||||
>
|
||||
Home Page
|
||||
</Link>
|
||||
<Link href="/account">My Account</Link>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="link"
|
||||
className="!h-auto hover:!no-underline justify-start gap-2 px-0 py-1 text-start text-muted-foreground hover:text-foreground"
|
||||
onClick={props.logout}
|
||||
variant="ghost"
|
||||
className="flex items-center justify-between gap-2 px-3 text-muted-foreground text-sm hover:text-foreground"
|
||||
onClick={() => {
|
||||
setTheme(theme === "light" ? "dark" : "light");
|
||||
}}
|
||||
>
|
||||
<LogOutIcon className="size-4" />
|
||||
Log Out
|
||||
<p className="text-sm">Theme</p>
|
||||
|
||||
<div className="ml-auto flex items-center gap-2 rounded-lg border border-border px-3 py-1.5 text-foreground">
|
||||
<Sun
|
||||
className={cn(
|
||||
"size-4",
|
||||
theme === "light" ? "opacity-100" : "opacity-30",
|
||||
)}
|
||||
/>
|
||||
<Moon
|
||||
className={cn(
|
||||
"size-4",
|
||||
theme === "dark" ? "opacity-100" : "opacity-30",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
asChild
|
||||
variant="ghost"
|
||||
className="justify-start px-3 text-muted-foreground text-sm hover:text-foreground"
|
||||
>
|
||||
<Link href="/">Home Page</Link>
|
||||
</Button>
|
||||
|
||||
{props.account && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() => {
|
||||
props.logout();
|
||||
setIsOpen(false);
|
||||
}}
|
||||
className="justify-between gap-2 px-3 text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
Log Out
|
||||
<LogOutIcon className="size-4" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
|
||||
@@ -8,8 +8,8 @@ import {
|
||||
LogOutIcon,
|
||||
MenuIcon,
|
||||
Moon,
|
||||
SettingsIcon,
|
||||
Sun,
|
||||
UserRoundIcon,
|
||||
XIcon,
|
||||
} from "lucide-react";
|
||||
import { useTheme } from "next-themes";
|
||||
@@ -96,8 +96,8 @@ export function MobileBurgerMenuButton(
|
||||
href="/account"
|
||||
className="flex items-center gap-2 text-base text-muted-foreground hover:text-foreground "
|
||||
>
|
||||
<SettingsIcon className="size-4" />
|
||||
Account Settings
|
||||
<UserRoundIcon className="size-4" />
|
||||
My Account
|
||||
</Link>
|
||||
|
||||
<Button
|
||||
@@ -154,7 +154,7 @@ export function MobileBurgerMenuButton(
|
||||
href="/trending"
|
||||
className="text-muted-foreground hover:text-foreground "
|
||||
>
|
||||
Popular Contracts
|
||||
Trending Contracts
|
||||
</Link>
|
||||
|
||||
<Link
|
||||
|
||||
@@ -7,7 +7,6 @@ import { useTheme } from "next-themes";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { Suspense, useEffect, useState } from "react";
|
||||
import { ConnectEmbed } from "thirdweb/react";
|
||||
import { getCookie } from "../../stores/SyncStoreToCookies";
|
||||
import { ThirdwebMiniLogo } from "../components/ThirdwebMiniLogo";
|
||||
import { getSDKTheme } from "../components/sdk-component-theme";
|
||||
import { doLogin, doLogout, getLoginPayload, isLoggedIn } from "./auth-actions";
|
||||
@@ -49,12 +48,7 @@ function CustomConnectEmmbed() {
|
||||
if (nextSearchParam && isValidRedirectPath(nextSearchParam)) {
|
||||
router.replace(nextSearchParam);
|
||||
} else {
|
||||
const dashboardType = getCookie("x-dashboard-type");
|
||||
if (dashboardType === "team") {
|
||||
router.replace("/team");
|
||||
} else {
|
||||
router.replace("/dashboard");
|
||||
}
|
||||
router.replace("/team");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user