From feb4fd0908fb0a0c8c584cd16fb7290b4f49dc4a Mon Sep 17 00:00:00 2001 From: MananTank Date: Wed, 22 Jan 2025 23:30:11 +0000 Subject: [PATCH] [TOOL-3123] Dashboard: Order projects by total wallets (#6026) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## PR-Codex overview This PR focuses on enhancing the `CopyTextButton` component, optimizing the project fetching logic, and improving project display features by integrating total wallet connections in the dashboard. ### Detailed summary - Changed `tooltip` type in `CopyTextButton` to `string | undefined`. - Updated the API call in `getChangelog` to limit results to 7. - Replaced cookie-based token retrieval with `getAuthToken` in `getProjects`. - Added `getProjectsWithTotalWallets` to calculate total wallet connections. - Updated `TeamProjectsPage` to display projects with total connections. - Modified sorting logic in `TeamProjectsPage` to include total connections. - Replaced `CopyButton` with `CopyTextButton` in `ProjectCard`. - Enhanced project display to show total users. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- apps/dashboard/src/@/api/projects.ts | 15 +- .../src/@/components/ui/CopyTextButton.tsx | 2 +- .../src/app/team/[team_slug]/(team)/page.tsx | 36 +++- .../(team)/~/projects/TeamProjectsPage.tsx | 175 ++++++++++++------ .../src/components/dashboard/Changelog.tsx | 2 +- 5 files changed, 153 insertions(+), 77 deletions(-) diff --git a/apps/dashboard/src/@/api/projects.ts b/apps/dashboard/src/@/api/projects.ts index ef2cbdbd558..f45ecf02496 100644 --- a/apps/dashboard/src/@/api/projects.ts +++ b/apps/dashboard/src/@/api/projects.ts @@ -1,7 +1,6 @@ import "server-only"; -import { COOKIE_ACTIVE_ACCOUNT, COOKIE_PREFIX_TOKEN } from "@/constants/cookie"; import { API_SERVER_URL } from "@/constants/env"; -import { cookies } from "next/headers"; +import { getAuthToken } from "../../app/api/lib/getAuthToken"; export type Project = { id: string; @@ -21,11 +20,7 @@ export type Project = { }; export async function getProjects(teamSlug: string) { - const cookiesManager = await cookies(); - const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value; - const token = activeAccount - ? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value - : null; + const token = await getAuthToken(); if (!token) { return []; @@ -46,11 +41,7 @@ export async function getProjects(teamSlug: string) { } export async function getProject(teamSlug: string, projectSlug: string) { - const cookiesManager = await cookies(); - const activeAccount = cookiesManager.get(COOKIE_ACTIVE_ACCOUNT)?.value; - const token = activeAccount - ? cookiesManager.get(COOKIE_PREFIX_TOKEN + activeAccount)?.value - : null; + const token = await getAuthToken(); if (!token) { return null; diff --git a/apps/dashboard/src/@/components/ui/CopyTextButton.tsx b/apps/dashboard/src/@/components/ui/CopyTextButton.tsx index 0b9a6b12d89..b1a9c14d7fb 100644 --- a/apps/dashboard/src/@/components/ui/CopyTextButton.tsx +++ b/apps/dashboard/src/@/components/ui/CopyTextButton.tsx @@ -9,7 +9,7 @@ import { ToolTipLabel } from "./tooltip"; export function CopyTextButton(props: { textToShow: string; textToCopy: string; - tooltip: string; + tooltip: string | undefined; className?: string; iconClassName?: string; variant?: diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/page.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/page.tsx index 2903795af20..a51c652eda7 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/page.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/page.tsx @@ -1,4 +1,5 @@ -import { getProjects } from "@/api/projects"; +import { getWalletConnections } from "@/api/analytics"; +import { type Project, getProjects } from "@/api/projects"; import { getTeamBySlug } from "@/api/team"; import { Changelog } from "components/dashboard/Changelog"; import { redirect } from "next/navigation"; @@ -15,12 +16,13 @@ export default async function Page(props: { } const projects = await getProjects(params.team_slug); + const projectsWithTotalWallets = await getProjectsWithTotalWallets(projects); return (

Projects

- +

@@ -31,3 +33,33 @@ export default async function Page(props: {

); } + +async function getProjectsWithTotalWallets( + projects: Project[], +): Promise> { + return Promise.all( + projects.map(async (p) => { + try { + const data = await getWalletConnections({ + clientId: p.publishableKey, + period: "all", + }); + + let totalConnections = 0; + for (const d of data) { + totalConnections += d.totalConnections; + } + + return { + ...p, + totalConnections, + }; + } catch { + return { + ...p, + totalConnections: 0, + }; + } + }), + ); +} diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsPage.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsPage.tsx index a16a4223a95..488c7f3c3f0 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsPage.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsPage.tsx @@ -3,7 +3,7 @@ import type { Project } from "@/api/projects"; import type { Team } from "@/api/team"; import { ProjectAvatar } from "@/components/blocks/Avatars/ProjectAvatar"; -import { CopyButton } from "@/components/ui/CopyButton"; +import { CopyTextButton } from "@/components/ui/CopyTextButton"; import { Button } from "@/components/ui/button"; import { DropdownMenu, @@ -11,6 +11,11 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Input } from "@/components/ui/input"; +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover"; import { Select, SelectContent, @@ -19,43 +24,58 @@ import { } from "@/components/ui/select"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { LazyCreateAPIKeyDialog } from "components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog"; -import { ChevronDownIcon, PlusIcon, SearchIcon } from "lucide-react"; +import { + ChevronDownIcon, + EllipsisVerticalIcon, + PlusIcon, + SearchIcon, +} from "lucide-react"; import Link from "next/link"; -import { useState } from "react"; +import { useMemo, useState } from "react"; + +type SortById = "name" | "createdAt" | "totalConnections"; -type SortById = "name" | "createdAt"; +type ProjectWithTotalConnections = Project & { totalConnections: number }; export function TeamProjectsPage(props: { - projects: Project[]; + projects: ProjectWithTotalConnections[]; team: Team; }) { const { projects } = props; const [searchTerm, setSearchTerm] = useState(""); - const [sortBy, setSortBy] = useState("createdAt"); + const [sortBy, setSortBy] = useState("totalConnections"); const [isCreateProjectDialogOpen, setIsCreateProjectDialogOpen] = useState(false); const router = useDashboardRouter(); - let projectsToShow = !searchTerm - ? projects - : projects.filter( - (project) => - project.name.toLowerCase().includes(searchTerm.toLowerCase()) || - project.publishableKey - .toLowerCase() - .includes(searchTerm.toLowerCase()), + const projectsToShow = useMemo(() => { + let _projectsToShow = !searchTerm + ? projects + : projects.filter( + (project) => + project.name.toLowerCase().includes(searchTerm.toLowerCase()) || + project.publishableKey + .toLowerCase() + .includes(searchTerm.toLowerCase()), + ); + + if (sortBy === "name") { + _projectsToShow = _projectsToShow.sort((a, b) => + a.name.localeCompare(b.name), + ); + } else if (sortBy === "createdAt") { + _projectsToShow = _projectsToShow.sort( + (a, b) => + new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), + ); + } else if (sortBy === "totalConnections") { + _projectsToShow = _projectsToShow.sort( + (a, b) => b.totalConnections - a.totalConnections, ); + } - if (sortBy === "name") { - projectsToShow = projectsToShow.sort((a, b) => - a.name.localeCompare(b.name), - ); - } else if (sortBy === "createdAt") { - projectsToShow = projectsToShow.sort( - (a, b) => - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), - ); - } + return _projectsToShow; + }, [searchTerm, sortBy, projects]); return (
@@ -75,19 +95,29 @@ export function TeamProjectsPage(props: { {/* Projects */} {projectsToShow.length === 0 ? ( -
-
-

No projects created

- -
-
+ <> + {searchTerm !== "" ? ( +
+
+

No projects found

+
+
+ ) : ( +
+
+

No projects created

+ +
+
+ )} + ) : (
{projectsToShow.map((project) => { @@ -118,7 +148,7 @@ export function TeamProjectsPage(props: { } function ProjectCard(props: { - project: Project; + project: ProjectWithTotalConnections; team_slug: string; }) { const { project, team_slug } = props; @@ -130,34 +160,51 @@ function ProjectCard(props: { {/* TODO - set image */} -
-
- -

{project.name}

- - -
+
+ +

{project.name}

+ -

- {truncate(project.publishableKey, 32)} +

+ {project.totalConnections} + Total Users

+ + + + + + + + + +
); } -function truncate(str: string, stringLimit: number) { - return str.length > stringLimit ? `${str.slice(0, stringLimit)}...` : str; -} - function SearchInput(props: { value: string; onValueChange: (value: string) => void; @@ -209,10 +256,11 @@ function SelectBy(props: { value: SortById; onChange: (value: SortById) => void; }) { - const values: SortById[] = ["name", "createdAt"]; + const values: SortById[] = ["name", "createdAt", "totalConnections"]; const valueToLabel: Record = { name: "Name", createdAt: "Creation Date", + totalConnections: "Total Users", }; return ( @@ -223,7 +271,12 @@ function SelectBy(props: { }} > - Sort by {valueToLabel[props.value]} +
+ + Sort by + + {valueToLabel[props.value]} +
{values.map((value) => ( diff --git a/apps/dashboard/src/components/dashboard/Changelog.tsx b/apps/dashboard/src/components/dashboard/Changelog.tsx index 9beb929129d..4766fbfe012 100644 --- a/apps/dashboard/src/components/dashboard/Changelog.tsx +++ b/apps/dashboard/src/components/dashboard/Changelog.tsx @@ -47,7 +47,7 @@ export async function Changelog() { 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=10", + "https://thirdweb.ghost.io/ghost/api/content/posts/?key=49c62b5137df1c17ab6b9e46e3&fields=title,url,published_at&filter=tag:changelog&visibility:public&limit=7", ); const json = await res.json(); return json.posts as ChangelogItem[];