Skip to content

Commit

Permalink
[Feature] Initial Projects and Teams setup in dashboard (#3682)
Browse files Browse the repository at this point in the history
<!-- start pr-codex -->

## PR-Codex overview
This PR adds new pages and components related to team and project settings, navigation improvements, and styling updates.

### Detailed summary
- Added new pages for settings, contracts, and various connect options
- Implemented navigation improvements with NavLink and SidebarLink components
- Styling updates for scrollbar and badge variants
- Created components like TWAutoConnect and TeamTabs for better UI/UX

> The following files were skipped due to too many changes: `apps/dashboard/src/@/components/ui/badge.tsx`, `apps/dashboard/src/app/components/Header/TeamHeader/SearchInput.tsx`, `apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/ConnectMobileSidebar.tsx`, `apps/dashboard/src/app/team/[team_slug]/components/active-wallet-logo.tsx`, `apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/(chainPage)/layout.tsx`, `apps/dashboard/src/components/app-layouts/provider-setup.tsx`, `apps/dashboard/src/@/components/ui/select.tsx`, `apps/dashboard/src/@/components/blocks/BurgerMenuButton.tsx`, `apps/dashboard/.storybook/preview.tsx`, `apps/dashboard/src/components/icons/ChainIcon.tsx`, `apps/dashboard/src/app/team/[team_slug]/[project_slug]/connect/layout.tsx`, `apps/dashboard/src/@/api/team-members.ts`, `apps/dashboard/src/app/components/Header/TeamHeader/ProjectSelectorMobileMenuButton.tsx`, `apps/dashboard/src/app/team/[team_slug]/(team)/layout.tsx`, `apps/dashboard/src/app/components/Header/TeamHeader/TeamSelectorMobileMenuButton.tsx`, `apps/dashboard/src/app/components/Header/TeamHeader/TeamHeader.tsx`, `apps/dashboard/src/@/api/team.ts`, `apps/dashboard/src/app/team/[team_slug]/(team)/~/settings/layout.tsx`, `apps/dashboard/src/@/api/projects.ts`, `apps/dashboard/src/app/team/[team_slug]/[project_slug]/layout.tsx`, `apps/dashboard/src/app/components/Header/TeamHeader/ResourcesDropdownButton.tsx`, `apps/dashboard/src/app/components/Header/TeamHeader/TeamAndProjectSelectorPopoverButton.tsx`, `apps/dashboard/src/app/team/[team_slug]/components/account-button.client.tsx`, `apps/dashboard/src/app/components/Header/TeamHeader/ProjectSelectorUI.tsx`, `apps/dashboard/src/app/components/Header/TeamHeader/TeamSelectionUI.tsx`, `apps/dashboard/src/components/cmd-k-search/index.tsx`, `apps/dashboard/src/@/components/ui/tabs.tsx`, `apps/dashboard/src/stories/Header.stories.tsx`, `apps/dashboard/src/app/components/Header/TeamHeader/MobileBurgerMenuButton.tsx`, `apps/dashboard/src/app/components/Header/TeamHeader/TeamHeaderUI.tsx`, `apps/dashboard/src/app/team/[team_slug]/(team)/TeamOverviewPage.tsx`

> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`

<!-- end pr-codex -->
  • Loading branch information
jnsdls committed Aug 27, 2024
1 parent a716bbb commit 1ef1921
Show file tree
Hide file tree
Showing 61 changed files with 2,350 additions and 93 deletions.
3 changes: 1 addition & 2 deletions apps/dashboard/.storybook/main.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { StorybookConfig } from "@storybook/nextjs";

import { dirname, join } from "node:path";
import type { StorybookConfig } from "@storybook/nextjs";

/**
* This function is used to resolve the absolute path of a package.
Expand Down
15 changes: 12 additions & 3 deletions apps/dashboard/.storybook/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import type { Preview } from "@storybook/react";
import "@/styles/globals.css";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { Inter as interFont } from "next/font/google";
// biome-ignore lint/correctness/noUnusedImports: <explanation>
import React from "react";
import React, { useEffect } from "react";
import { cn } from "../src/@/lib/utils";

// Note: Wrapping the Stoy with AppRouterProviders makes the storybook server time SUPER SLOW
// so let's just not have it there - all stories should be independent from context anyway and just rely on props

const queryClient = new QueryClient();

const fontSans = interFont({
subsets: ["latin"],
variable: "--font-sans",
Expand All @@ -25,10 +28,16 @@ const preview: Preview = {

decorators: [
(Story) => {
useEffect(() => {
document.body.className = cn(
"font-sans antialiased",
fontSans.variable,
);
});
return (
<div className={cn("font-sans antialiased", fontSans.variable)}>
<QueryClientProvider client={queryClient}>
<Story />
</div>
</QueryClientProvider>
);
},
],
Expand Down
73 changes: 73 additions & 0 deletions apps/dashboard/src/@/api/projects.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import "server-only";
import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
import { cookies } from "next/headers";

const THIRDWEB_API_HOST =
process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb.com";

export type Project = {
id: string;
name: string;
createdAt: Date;
updatedAt: Date;
deletedAt: Date | null;
bannedAt: Date | null;
domains: string[];
bundleIds: string[];
redirectUrls: string[];
lastAccessedAt: Date | null;
slug: string;
teamId: string;
publishableKey: string;
// image: string; // TODO
};

export async function getProjects(teamSlug: string) {
const cookiesManager = cookies();
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
const token = activeAccount
? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
: null;

if (!token) {
return [];
}

const teamsRes = await fetch(
`${THIRDWEB_API_HOST}/v1/teams/${teamSlug}/projects`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
if (teamsRes.ok) {
return (await teamsRes.json())?.result as Project[];
}
return [];
}

export async function getProject(teamSlug: string, projectSlug: string) {
const cookiesManager = cookies();
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
const token = activeAccount
? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
: null;

if (!token) {
return null;
}

const teamsRes = await fetch(
`${THIRDWEB_API_HOST}/v1/teams/${teamSlug}/projects/${projectSlug}`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
if (teamsRes.ok) {
return (await teamsRes.json())?.result as Project;
}
return null;
}
53 changes: 53 additions & 0 deletions apps/dashboard/src/@/api/team-members.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import "server-only";
import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
import { cookies } from "next/headers";

const THIRDWEB_API_HOST =
process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb.com";

const TeamAccountRole = {
OWNER: "OWNER",
MEMBER: "MEMBER",
} as const;

export type TeamAccountRole =
(typeof TeamAccountRole)[keyof typeof TeamAccountRole];

export type TeamMember = {
account: {
name: string;
email: string | null;
};
} & {
deletedAt: Date | null;
accountId: string;
teamId: string;
createdAt: Date;
updatedAt: Date;
role: TeamAccountRole;
};

export async function getMembers(teamSlug: string) {
const cookiesManager = cookies();
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
const token = activeAccount
? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
: null;

if (!token) {
return [];
}

const teamsRes = await fetch(
`${THIRDWEB_API_HOST}/v1/teams/${teamSlug}/members`,
{
headers: {
Authorization: `Bearer ${token}`,
},
},
);
if (teamsRes.ok) {
return (await teamsRes.json())?.result as TeamMember[];
}
return [];
}
58 changes: 58 additions & 0 deletions apps/dashboard/src/@/api/team.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import "server-only";
import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie";
import { cookies } from "next/headers";

const THIRDWEB_API_HOST =
process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb.com";

export type Team = {
name: string;
slug: string;
// image: string; // -> TODO
billingPlan: "pro" | "growth" | "free";
billingStatus: "validPayment" | (string & {}); // what's the other value?
};

export async function getTeamBySlug(slug: string) {
const cookiesManager = cookies();
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
const token = activeAccount
? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
: null;

if (!token) {
return null;
}

const teamRes = await fetch(`${THIRDWEB_API_HOST}/v1/teams/${slug}`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (teamRes.ok) {
return (await teamRes.json())?.result as Team;
}
return null;
}

export async function getTeams() {
const cookiesManager = cookies();
const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value;
const token = activeAccount
? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value
: null;

if (!token) {
return [];
}

const teamsRes = await fetch(`${THIRDWEB_API_HOST}/v1/teams`, {
headers: {
Authorization: `Bearer ${token}`,
},
});
if (teamsRes.ok) {
return (await teamsRes.json())?.result as Team[];
}
return [];
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,20 @@

import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog";
import { MenuIcon } from "lucide-react";
import { useState } from "react";
import { NavLink } from "../ui/NavLink";

export function BurgerMenuButton(props: {
export function MobileSidebar(props: {
links?: { href: string; label: React.ReactNode }[];
footer?: React.ReactNode;
trigger: React.ReactNode;
}) {
const [isOpen, setIsOpen] = useState(false);
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
<DialogTrigger asChild>
<Button size="icon" variant="outline">
<MenuIcon strokeWidth={1} />
</Button>
</DialogTrigger>
<DialogTrigger asChild>{props.trigger}</DialogTrigger>
<DialogContent
className="p-6 rounded-xl"
className="p-4 rounded-t-xl rounded-b-none"
dialogCloseClassName="hidden"
onClick={(e) => {
if (e.target instanceof HTMLAnchorElement) {
Expand Down
2 changes: 1 addition & 1 deletion apps/dashboard/src/@/components/blocks/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { NavLink } from "../ui/NavLink";

export type SidebarContentProps = {
header?: React.ReactNode;
links?: { href: string; label: React.ReactNode }[];
links: { href: string; label: React.ReactNode }[];
className?: string;
};

Expand Down
3 changes: 2 additions & 1 deletion apps/dashboard/src/@/components/ui/NavLink.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,12 @@ export type NavButtonProps = {

export function NavLink(props: React.PropsWithChildren<NavButtonProps>) {
const pathname = usePathname();
const isActive = pathname === props.href;
const isActive = pathname ? pathname.startsWith(props.href) : false;
return (
<Link
href={props.href}
className={cn(props.className, isActive && props.activeClassName)}
target={props.href.startsWith("http") ? "_blank" : undefined}
>
{props.children}
</Link>
Expand Down
6 changes: 2 additions & 4 deletions apps/dashboard/src/@/components/ui/badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,14 @@ const badgeVariants = cva(
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
default: "border-transparent bg-primary/[0.08] text-primary",
secondary:
"border-transparent bg-accent text-accent-foreground hover:bg-accent/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
warning: "border-transparent bg-warning text-warning-foreground",
outline: "text-foreground",
success:
"border-transparent bg-success text-success-foreground hover:bg-success/80",
success: "border-transparent bg-success text-success-foreground",
},
},
defaultVariants: {
Expand Down
14 changes: 13 additions & 1 deletion apps/dashboard/src/@/components/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,19 @@ const SelectContent = React.forwardRef<
>(({ className, children, position = "popper", ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
// Fixes https://github.com/radix-ui/primitives/issues/1658
ref={(instance) => {
if (typeof ref === "function") {
ref(instance);
} else if (ref) {
ref.current = instance;
}
if (!instance) return;

instance.ontouchstart = (e) => {
e.preventDefault();
};
}}
className={cn(
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
Expand Down
Loading

0 comments on commit 1ef1921

Please sign in to comment.