Skip to content

Commit d03e687

Browse files
committed
[TOOL-3231] Show View Project Link after creating a project in Modal (#6055)
<!-- start pr-codex --> ## PR-Codex overview This PR introduces the `teamSlug` prop across multiple components to enhance API key management by associating it with specific teams. This allows for better project handling and user navigation within the dashboard. ### Detailed summary - Added `teamSlug` prop to `CreateAPIKeyModal` and related components. - Updated `embed-setup.tsx` to handle `teamSlug` as `undefined`. - Integrated `teamSlug` in `TeamProjectsPage` and `AccountHeader`. - Modified `CreateAPIKeyDialog` to include `teamSlug` in props. - Enhanced `APIKeyDetails` to fetch projects based on `teamSlug`. - Implemented conditional rendering for "View Project" button based on `teamSlug`. > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent e1c0dbe commit d03e687

File tree

6 files changed

+70
-4
lines changed

6 files changed

+70
-4
lines changed

apps/dashboard/src/app/(dashboard)/(chain)/[chain_id]/[contractAddress]/embed/embed-setup.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,7 @@ export const EmbedSetup: React.FC<EmbedSetupProps> = ({
323323
apiKeys.refetch();
324324
}}
325325
enableNebulaServiceByDefault={false}
326+
teamSlug={undefined}
326327
/>
327328

328329
<Alert variant="warning">

apps/dashboard/src/app/account/components/AccountHeader.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ export function AccountHeader(props: {
7070
// refresh projects
7171
router.refresh();
7272
}}
73+
teamSlug={
74+
createProjectDialogState.isOpen
75+
? createProjectDialogState.team.slug
76+
: undefined
77+
}
7378
enableNebulaServiceByDefault={
7479
createProjectDialogState.isOpen &&
7580
createProjectDialogState.team.enabledScopes.includes("nebula")

apps/dashboard/src/app/team/[team_slug]/(team)/~/projects/TeamProjectsPage.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ export function TeamProjectsPage(props: {
170170
<LazyCreateAPIKeyDialog
171171
open={isCreateProjectDialogOpen}
172172
onOpenChange={setIsCreateProjectDialogOpen}
173+
teamSlug={props.team.slug}
173174
onCreateAndComplete={() => {
174175
// refresh projects
175176
router.refresh();

apps/dashboard/src/app/team/components/TeamHeader/team-header-logged-in.client.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,11 @@ export function TeamHeaderLoggedIn(props: {
6565

6666
<LazyCreateAPIKeyDialog
6767
open={createProjectDialogState.isOpen}
68+
teamSlug={
69+
createProjectDialogState.isOpen
70+
? createProjectDialogState.team.slug
71+
: undefined
72+
}
6873
onOpenChange={() =>
6974
setCreateProjectDialogState({
7075
isOpen: false,

apps/dashboard/src/components/settings/ApiKeys/Create/CreateApiKeyModal.stories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ function Story(props: {
5353
createKeyMutation={mutation}
5454
prefill={props.prefill}
5555
enableNebulaServiceByDefault={false}
56+
teamSlug="foo"
5657
/>
5758

5859
<Button

apps/dashboard/src/components/settings/ApiKeys/Create/index.tsx

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { apiServerProxy } from "@/actions/proxies";
2+
import type { Project } from "@/api/projects";
13
import { CopyTextButton } from "@/components/ui/CopyTextButton";
24
import { DynamicHeight } from "@/components/ui/DynamicHeight";
35
import { Spinner } from "@/components/ui/Spinner/Spinner";
@@ -23,17 +25,18 @@ import {
2325
} from "@/components/ui/form";
2426
import { Input } from "@/components/ui/input";
2527
import { Textarea } from "@/components/ui/textarea";
28+
import { useDashboardRouter } from "@/lib/DashboardRouter";
2629
import {
2730
type ApiKey,
2831
type CreateKeyInput,
2932
useCreateApiKey,
3033
} from "@3rdweb-sdk/react/hooks/useApi";
3134
import { zodResolver } from "@hookform/resolvers/zod";
3235
import { DialogDescription } from "@radix-ui/react-dialog";
33-
import type { UseMutationResult } from "@tanstack/react-query";
36+
import { type UseMutationResult, useQuery } from "@tanstack/react-query";
3437
import { SERVICES } from "@thirdweb-dev/service-utils";
3538
import { useTrack } from "hooks/analytics/useTrack";
36-
import { ArrowLeftIcon } from "lucide-react";
39+
import { ArrowLeftIcon, ExternalLinkIcon } from "lucide-react";
3740
import { useState } from "react";
3841
import { useForm } from "react-hook-form";
3942
import { toast } from "sonner";
@@ -54,6 +57,7 @@ export type CreateAPIKeyDialogProps = {
5457
onCreateAndComplete?: () => void;
5558
prefill?: CreateAPIKeyPrefillOptions;
5659
enableNebulaServiceByDefault: boolean;
60+
teamSlug: string | undefined;
5761
};
5862

5963
const CreateAPIKeyDialog = (props: CreateAPIKeyDialogProps) => {
@@ -78,6 +82,7 @@ export const CreateAPIKeyDialogUI = (props: {
7882
>;
7983
prefill?: CreateAPIKeyPrefillOptions;
8084
enableNebulaServiceByDefault: boolean;
85+
teamSlug: string | undefined;
8186
}) => {
8287
const [screen, setScreen] = useState<
8388
{ id: "create" } | { id: "api-details"; key: ApiKey }
@@ -97,7 +102,7 @@ export const CreateAPIKeyDialogUI = (props: {
97102
}}
98103
>
99104
<DialogContent
100-
className="z-[10001] p-0"
105+
className="z-[10001] overflow-hidden p-0"
101106
dialogOverlayClassName="z-[10000]"
102107
dialogCloseClassName={screen.id === "api-details" ? "hidden" : ""}
103108
>
@@ -116,6 +121,7 @@ export const CreateAPIKeyDialogUI = (props: {
116121
{screen.id === "api-details" && (
117122
<APIKeyDetails
118123
apiKey={screen.key}
124+
teamSlug={props.teamSlug}
119125
onComplete={() => {
120126
onOpenChange(false);
121127
setScreen({ id: "create" });
@@ -402,9 +408,35 @@ function DomainsAlert(props: {
402408
function APIKeyDetails(props: {
403409
apiKey: ApiKey;
404410
onComplete: () => void;
411+
teamSlug: string | undefined;
405412
}) {
406413
const { apiKey } = props;
407414
const [secretStored, setSecretStored] = useState(false);
415+
const router = useDashboardRouter();
416+
417+
// get the project.slug for the apiKey to render "View Project" button
418+
const projectQuery = useQuery({
419+
queryKey: ["project", props.teamSlug, apiKey.id],
420+
queryFn: async () => {
421+
const res = await apiServerProxy<{
422+
result: Project[];
423+
}>({
424+
method: "GET",
425+
pathname: `/v1/teams/${props.teamSlug}/projects`,
426+
});
427+
428+
if (!res.ok) {
429+
throw new Error(res.error);
430+
}
431+
432+
const projects = res.data.result;
433+
const project = projects.find((p) => p.publishableKey === apiKey.key);
434+
return project || null;
435+
},
436+
enabled: !!props.teamSlug,
437+
});
438+
439+
const projectSlug = projectQuery.data?.slug;
408440

409441
return (
410442
<div>
@@ -474,10 +506,31 @@ function APIKeyDetails(props: {
474506
type="button"
475507
onClick={props.onComplete}
476508
disabled={!secretStored}
509+
variant="outline"
477510
className="min-w-28 gap-2"
478511
>
479-
Complete
512+
{props.teamSlug ? "Close" : "Complete"}
480513
</Button>
514+
515+
{props.teamSlug && (
516+
<Button
517+
onClick={() => {
518+
if (!projectSlug) {
519+
return;
520+
}
521+
router.push(`/team/${props.teamSlug}/${projectSlug}`);
522+
}}
523+
disabled={!secretStored || !projectSlug}
524+
className="min-w-28 gap-2"
525+
>
526+
View Project
527+
{projectQuery.isPending ? (
528+
<Spinner className="size-4" />
529+
) : (
530+
<ExternalLinkIcon className="size-4" />
531+
)}
532+
</Button>
533+
)}
481534
</DialogFooter>
482535
</div>
483536
);

0 commit comments

Comments
 (0)