From de362ba2f5e31e547e28829beeb6451c62c9d727 Mon Sep 17 00:00:00 2001 From: MananTank Date: Fri, 13 Sep 2024 23:08:24 +0000 Subject: [PATCH] Refactor Create API Key Modal, Add in teams for creating projects (#4583) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Problem solved Short description of the bug fixed or feature added --- ## PR-Codex overview This PR adds functionality to create projects in the dashboard UI. ### Detailed summary - Added `createProject` functionality to various components - Updated global CSS variables - Created `LazyCreateAPIKeyDialog` for project creation dialog - Updated UI components for project creation functionality > The following files were skipped due to too many changes: `apps/dashboard/src/stories/stubs.ts`, `apps/dashboard/src/pages/dashboard/settings/api-keys/index.tsx`, `apps/dashboard/src/components/settings/ApiKeys/Create/CreateApiKeyModal.stories.tsx`, `apps/dashboard/src/app/team/[team_slug]/(team)/TeamOverviewPage.tsx`, `apps/dashboard/src/components/settings/ApiKeys/Create/index.tsx` > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` --- apps/dashboard/src/@/components/ui/dialog.tsx | 2 +- apps/dashboard/src/@/styles/globals.css | 3 +- .../src/@3rdweb-sdk/react/hooks/useApi.ts | 2 +- .../app/account/components/AccountHeader.tsx | 16 +- .../components/AccountHeaderUI.stories.tsx | 1 + .../account/components/AccountHeaderUI.tsx | 2 + .../[team_slug]/(team)/TeamOverviewPage.tsx | 51 +- .../ProjectSelectorMobileMenuButton.tsx | 9 +- .../TeamHeader/ProjectSelectorUI.tsx | 9 +- .../TeamAndProjectSelectorPopoverButton.tsx | 5 + .../team/components/TeamHeader/TeamHeader.tsx | 16 +- .../TeamHeader/TeamHeaderUI.stories.tsx | 4 + .../components/TeamHeader/TeamHeaderUI.tsx | 4 + .../InAppWalletSettingsUI.stories.tsx | 27 +- .../Create/CreateApiKeyModal.stories.tsx | 89 +++ .../settings/ApiKeys/Create/General.tsx | 105 ---- .../settings/ApiKeys/Create/Keys.tsx | 51 -- .../ApiKeys/Create/LazyCreateAPIKeyDialog.tsx | 29 + .../settings/ApiKeys/Create/index.tsx | 571 +++++++++++++----- .../dashboard/settings/api-keys/index.tsx | 18 +- apps/dashboard/src/stories/stubs.ts | 44 ++ 21 files changed, 693 insertions(+), 365 deletions(-) create mode 100644 apps/dashboard/src/components/settings/ApiKeys/Create/CreateApiKeyModal.stories.tsx delete mode 100644 apps/dashboard/src/components/settings/ApiKeys/Create/General.tsx delete mode 100644 apps/dashboard/src/components/settings/ApiKeys/Create/Keys.tsx create mode 100644 apps/dashboard/src/components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog.tsx diff --git a/apps/dashboard/src/@/components/ui/dialog.tsx b/apps/dashboard/src/@/components/ui/dialog.tsx index b39c12936..4c1cbac00 100644 --- a/apps/dashboard/src/@/components/ui/dialog.tsx +++ b/apps/dashboard/src/@/components/ui/dialog.tsx @@ -41,7 +41,7 @@ const DialogContent = React.forwardRef< { // log out the user @@ -35,12 +38,23 @@ export function AccountHeader(props: { email: myAccountQuery.data?.email, logout: logout, connectButton: , + createProject: () => setIsCreateProjectDialogOpen(true), }; return (
+ + { + // refresh projects + router.refresh(); + }} + />
); } diff --git a/apps/dashboard/src/app/account/components/AccountHeaderUI.stories.tsx b/apps/dashboard/src/app/account/components/AccountHeaderUI.stories.tsx index e4bb0874e..fbfa6fa16 100644 --- a/apps/dashboard/src/app/account/components/AccountHeaderUI.stories.tsx +++ b/apps/dashboard/src/app/account/components/AccountHeaderUI.stories.tsx @@ -53,6 +53,7 @@ function Variants(props: { email="eng@thirdweb.com" logout={() => {}} connectButton={} + createProject={() => {}} /> diff --git a/apps/dashboard/src/app/account/components/AccountHeaderUI.tsx b/apps/dashboard/src/app/account/components/AccountHeaderUI.tsx index 0e43d5ed5..a8d5dbce2 100644 --- a/apps/dashboard/src/app/account/components/AccountHeaderUI.tsx +++ b/apps/dashboard/src/app/account/components/AccountHeaderUI.tsx @@ -14,6 +14,7 @@ export type AccountHeaderCompProps = { logout: () => void; connectButton: React.ReactNode; teamsAndProjects: Array<{ team: Team; projects: Project[] }>; + createProject: () => void; }; export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) { @@ -46,6 +47,7 @@ export function AccountHeaderDesktopUI(props: AccountHeaderCompProps) { currentTeam={undefined} teamsAndProjects={props.teamsAndProjects} focus="team-selection" + createProject={props.createProject} /> diff --git a/apps/dashboard/src/app/team/[team_slug]/(team)/TeamOverviewPage.tsx b/apps/dashboard/src/app/team/[team_slug]/(team)/TeamOverviewPage.tsx index df19e5df1..bc722a05f 100644 --- a/apps/dashboard/src/app/team/[team_slug]/(team)/TeamOverviewPage.tsx +++ b/apps/dashboard/src/app/team/[team_slug]/(team)/TeamOverviewPage.tsx @@ -2,18 +2,16 @@ import type { Project } from "@/api/projects"; import { Button } from "@/components/ui/button"; -import { Input } from "@/components/ui/input"; -import { format } from "date-fns"; -import { ChevronDownIcon, SearchIcon } from "lucide-react"; -import Link from "next/link"; -import { useState } from "react"; - -import { Badge } from "@/components/ui/badge"; import { DropdownMenu, DropdownMenuContent, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { Input } from "@/components/ui/input"; +import { format } from "date-fns"; +import { ChevronDownIcon, SearchIcon } from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; import { CopyButton } from "@/components/ui/CopyButton"; import { @@ -22,6 +20,8 @@ import { SelectItem, SelectTrigger, } from "@/components/ui/select"; +import { useDashboardRouter } from "@/lib/DashboardRouter"; +import { LazyCreateAPIKeyDialog } from "../../../../components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog"; type SortyById = "name" | "createdAt"; @@ -32,6 +32,9 @@ export function TeamOverviewPage(props: { const { projects } = props; const [searchTerm, setSearchTerm] = useState(""); const [sortBy, setSortBy] = useState("createdAt"); + const [isCreateProjectDialogOpen, setIsCreateProjectDialogOpen] = + useState(false); + const router = useDashboardRouter(); let projectsToShow = !searchTerm ? projects @@ -63,7 +66,10 @@ export function TeamOverviewPage(props: {
- + setIsCreateProjectDialogOpen(true)} + teamMembersSettingsPath={`/team/${props.team_slug}/~/settings/members`} + />
@@ -89,6 +95,16 @@ export function TeamOverviewPage(props: { )}
+ + { + // refresh projects + router.refresh(); + }} + />
); } @@ -153,7 +169,10 @@ function SearchInput(props: { ); } -function AddNewButton() { +function AddNewButton(props: { + createProject: () => void; + teamMembersSettingsPath: string; +}) { return ( @@ -164,15 +183,15 @@ function AddNewButton() {
- {/* TODO - remove soon when we have it */} - - {/* TODO - remove soon when we have it */} -
diff --git a/apps/dashboard/src/app/team/components/TeamHeader/ProjectSelectorMobileMenuButton.tsx b/apps/dashboard/src/app/team/components/TeamHeader/ProjectSelectorMobileMenuButton.tsx index 863b8d1de..10b0ce187 100644 --- a/apps/dashboard/src/app/team/components/TeamHeader/ProjectSelectorMobileMenuButton.tsx +++ b/apps/dashboard/src/app/team/components/TeamHeader/ProjectSelectorMobileMenuButton.tsx @@ -6,19 +6,22 @@ import { DynamicHeight } from "@/components/ui/DynamicHeight"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogTrigger } from "@/components/ui/dialog"; import { ChevronsUpDownIcon } from "lucide-react"; +import { useState } from "react"; import { ProjectSelectorUI } from "./ProjectSelectorUI"; type ProjectSelectorMobileMenuButtonProps = { currentProject: Project; projects: Project[]; team: Team; + createProject: () => void; }; export function ProjectSelectorMobileMenuButton( props: ProjectSelectorMobileMenuButtonProps, ) { + const [open, setOpen] = useState(false); return ( - + {/* Trigger */} diff --git a/apps/dashboard/src/app/team/components/TeamHeader/TeamAndProjectSelectorPopoverButton.tsx b/apps/dashboard/src/app/team/components/TeamHeader/TeamAndProjectSelectorPopoverButton.tsx index 0b2d839a4..8e8a1e787 100644 --- a/apps/dashboard/src/app/team/components/TeamHeader/TeamAndProjectSelectorPopoverButton.tsx +++ b/apps/dashboard/src/app/team/components/TeamHeader/TeamAndProjectSelectorPopoverButton.tsx @@ -19,6 +19,7 @@ type TeamSwitcherProps = { currentProject: Project | undefined; teamsAndProjects: Array<{ team: Team; projects: Project[] }>; focus: "project-selection" | "team-selection"; + createProject: () => void; }; export function TeamAndProjectSelectorPopoverButton(props: TeamSwitcherProps) { @@ -80,6 +81,10 @@ export function TeamAndProjectSelectorPopoverButton(props: TeamSwitcherProps) { currentProject={props.currentProject} projects={projectsToShow} team={projectsToShowOfTeam} + createProject={() => { + setOpen(false); + props.createProject(); + }} /> )} diff --git a/apps/dashboard/src/app/team/components/TeamHeader/TeamHeader.tsx b/apps/dashboard/src/app/team/components/TeamHeader/TeamHeader.tsx index 0145f3458..59c45ad80 100644 --- a/apps/dashboard/src/app/team/components/TeamHeader/TeamHeader.tsx +++ b/apps/dashboard/src/app/team/components/TeamHeader/TeamHeader.tsx @@ -5,8 +5,9 @@ import type { Team } from "@/api/team"; import { useDashboardRouter } from "@/lib/DashboardRouter"; import { CustomConnectWallet } from "@3rdweb-sdk/react/components/connect-wallet"; import { useAccount } from "@3rdweb-sdk/react/hooks/useApi"; -import { useCallback } from "react"; +import { useCallback, useState } from "react"; import { useActiveWallet, useDisconnect } from "thirdweb/react"; +import { LazyCreateAPIKeyDialog } from "../../../../components/settings/ApiKeys/Create/LazyCreateAPIKeyDialog"; import { type TeamHeaderCompProps, TeamHeaderDesktopUI, @@ -18,6 +19,8 @@ export function TeamHeader(props: { teamsAndProjects: Array<{ team: Team; projects: Project[] }>; currentProject: Project | undefined; }) { + const [isCreateProjectDialogOpen, setIsCreateProjectDialogOpen] = + useState(false); const myAccountQuery = useAccount(); const activeWallet = useActiveWallet(); const { disconnect } = useDisconnect(); @@ -45,12 +48,23 @@ export function TeamHeader(props: { email: myAccountQuery.data?.email, logout: logout, connectButton: , + createProject: () => setIsCreateProjectDialogOpen(true), }; return (
+ + { + // refresh projects + router.refresh(); + }} + />
); } diff --git a/apps/dashboard/src/app/team/components/TeamHeader/TeamHeaderUI.stories.tsx b/apps/dashboard/src/app/team/components/TeamHeader/TeamHeaderUI.stories.tsx index 1659ac61a..a468cef68 100644 --- a/apps/dashboard/src/app/team/components/TeamHeader/TeamHeaderUI.stories.tsx +++ b/apps/dashboard/src/app/team/components/TeamHeader/TeamHeaderUI.stories.tsx @@ -52,6 +52,7 @@ function Variants(props: { email="eng@thirdweb.com" logout={() => {}} connectButton={} + createProject={() => {}} /> @@ -63,6 +64,7 @@ function Variants(props: { email={undefined} logout={() => {}} connectButton={} + createProject={() => {}} /> @@ -74,6 +76,7 @@ function Variants(props: { email="eng@thirdweb.com" logout={() => {}} connectButton={} + createProject={() => {}} /> @@ -85,6 +88,7 @@ function Variants(props: { email="eng@thirdweb.com" logout={() => {}} connectButton={} + createProject={() => {}} /> diff --git a/apps/dashboard/src/app/team/components/TeamHeader/TeamHeaderUI.tsx b/apps/dashboard/src/app/team/components/TeamHeader/TeamHeaderUI.tsx index 485a03241..09e441d11 100644 --- a/apps/dashboard/src/app/team/components/TeamHeader/TeamHeaderUI.tsx +++ b/apps/dashboard/src/app/team/components/TeamHeader/TeamHeaderUI.tsx @@ -19,6 +19,7 @@ export type TeamHeaderCompProps = { email: string | undefined; logout: () => void; connectButton: React.ReactNode; + createProject: () => void; }; export function TeamHeaderDesktopUI(props: TeamHeaderCompProps) { @@ -56,6 +57,7 @@ export function TeamHeaderDesktopUI(props: TeamHeaderCompProps) { currentTeam={props.currentTeam} teamsAndProjects={props.teamsAndProjects} focus="team-selection" + createProject={props.createProject} /> @@ -75,6 +77,7 @@ export function TeamHeaderDesktopUI(props: TeamHeaderCompProps) { currentTeam={props.currentTeam} teamsAndProjects={props.teamsAndProjects} focus="project-selection" + createProject={props.createProject} /> @@ -144,6 +147,7 @@ export function TeamHeaderMobileUI(props: TeamHeaderCompProps) { currentProject={props.currentProject} projects={projects} team={props.currentTeam} + createProject={props.createProject} /> diff --git a/apps/dashboard/src/components/embedded-wallets/Configure/InAppWalletSettingsUI.stories.tsx b/apps/dashboard/src/components/embedded-wallets/Configure/InAppWalletSettingsUI.stories.tsx index 51108f90a..f931d3b60 100644 --- a/apps/dashboard/src/components/embedded-wallets/Configure/InAppWalletSettingsUI.stories.tsx +++ b/apps/dashboard/src/components/embedded-wallets/Configure/InAppWalletSettingsUI.stories.tsx @@ -1,5 +1,5 @@ -import type { ApiKey, ApiKeyService } from "@3rdweb-sdk/react/hooks/useApi"; import type { Meta, StoryObj } from "@storybook/react"; +import { createApiKeyStub } from "../../../stories/stubs"; import { mobileViewport } from "../../../stories/utils"; import { InAppWalletSettingsUI } from "./index"; @@ -46,30 +46,7 @@ export const FreePlanMobile: Story = { }, }; -const embeddedWalletService: ApiKeyService = { - id: "embeddedWallets", - name: "embeddedWallets", // important - targetAddresses: [], - actions: [], -}; - -const apiKeyStub: ApiKey = { - id: "api-key-id-foo", - name: "api key name foo", - key: "key-foobar", - accountId: "account-id-foo", - bundleIds: ["bundle-id-foo", "bundle-id-bar"], - createdAt: new Date().toISOString(), - creatorWalletAddress: "0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37", - domains: ["example1.com", "example2.com"], - secretMasked: "", - walletAddresses: ["0x1F846F6DAE38E1C88D71EAA191760B15f38B7A37"], - redirectUrls: [], - revokedAt: "", - lastAccessedAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - services: [embeddedWalletService], -}; +const apiKeyStub = createApiKeyStub(); function Variants(props: { canEditAdvancedFeatures: boolean; diff --git a/apps/dashboard/src/components/settings/ApiKeys/Create/CreateApiKeyModal.stories.tsx b/apps/dashboard/src/components/settings/ApiKeys/Create/CreateApiKeyModal.stories.tsx new file mode 100644 index 000000000..b2b919817 --- /dev/null +++ b/apps/dashboard/src/components/settings/ApiKeys/Create/CreateApiKeyModal.stories.tsx @@ -0,0 +1,89 @@ +import { Button } from "@/components/ui/button"; +import type { CreateKeyInput } from "@3rdweb-sdk/react/hooks/useApi"; +import type { Meta, StoryObj } from "@storybook/react"; +import { useMutation } from "@tanstack/react-query"; +import { useState } from "react"; +import { Toaster } from "sonner"; +import { CreateAPIKeyDialogUI } from "."; +import { createApiKeyStub } from "../../../../stories/stubs"; +import { mobileViewport } from "../../../../stories/utils"; + +const meta = { + title: "Project/Create Project Modal", + component: Story, + parameters: { + nextjs: { + appDirectory: true, + }, + }, +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +export const DesktopProjectWording: Story = { + args: { + wording: "project", + }, +}; + +export const MobileProjectWording: Story = { + args: { + wording: "project", + }, + parameters: { + viewport: mobileViewport("iphone14"), + }, +}; + +export const DesktopAPIKeyWording: Story = { + args: { + wording: "api-key", + }, +}; + +export const MobileAPIKeyWording: Story = { + args: { + wording: "api-key", + }, + parameters: { + viewport: mobileViewport("iphone14"), + }, +}; + +function Story(props: { + wording: "project" | "api-key"; +}) { + const [isOpen, setIsOpen] = useState(true); + const mutation = useMutation({ + mutationFn: async (input: CreateKeyInput) => { + console.log("Creating API key with", input); + await new Promise((resolve) => setTimeout(resolve, 1000)); + const apiKey = createApiKeyStub(); + apiKey.name = input.name || apiKey.name; + return apiKey; + }, + }); + + return ( +
+ + + + + +
+ ); +} diff --git a/apps/dashboard/src/components/settings/ApiKeys/Create/General.tsx b/apps/dashboard/src/components/settings/ApiKeys/Create/General.tsx deleted file mode 100644 index 3658053b6..000000000 --- a/apps/dashboard/src/components/settings/ApiKeys/Create/General.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { - Flex, - FormControl, - HStack, - Input, - ListItem, - Textarea, - UnorderedList, -} from "@chakra-ui/react"; -import type { UseFormReturn } from "react-hook-form"; -import { - Checkbox, - FormErrorMessage, - FormHelperText, - FormLabel, - Text, -} from "tw-components"; -import type { ApiKeyCreateValidationSchema } from "../validations"; - -interface CreateGeneralProps { - // biome-ignore lint/suspicious/noExplicitAny: FIXME - form: UseFormReturn; -} - -export const CreateGeneral: React.FC = ({ form }) => { - return ( - - - Key name - - - {form.getFieldState("name", form.formState).error?.message} - - - - - - - Prevent third-parties from using your Client ID by restricting - access to allowed domains.{" "} - - Highly recommended - {" "} - for frontend applications. - - - - - Allowed Domains - - { - form.setValue("domains", e.target.checked ? "*" : "", { - shouldDirty: true, - }); - }} - > - Allow all domains - - - -