Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ export function ProjectGeneralSettingsPage(props: {
paths: EditProjectUIPaths;
onKeyUpdated: (() => void) | undefined;
wording: "project" | "api-key";
// TODO: remove this when this component is not used in the project settings page
hideFields?: {
name: boolean;
};
}) {
const updateMutation = useUpdateApiKey();
const deleteMutation = useRevokeApiKey();
Expand All @@ -64,6 +68,7 @@ export function ProjectGeneralSettingsPage(props: {
deleteMutation={deleteMutation}
paths={props.paths}
onKeyUpdated={props.onKeyUpdated}
hideFields={props.hideFields}
/>
);
}
Expand All @@ -84,6 +89,9 @@ interface EditApiKeyProps {
deleteMutation: DeleteMutation;
paths: EditProjectUIPaths;
onKeyUpdated: (() => void) | undefined;
hideFields?: {
name: boolean;
};
}

type UpdateAPIForm = UseFormReturn<ApiKeyValidationSchema>;
Expand Down Expand Up @@ -214,12 +222,14 @@ export const ProjectGeneralSettingsPageUI: React.FC<EditApiKeyProps> = (
autoComplete="off"
>
<div className="flex flex-col gap-8">
<ProjectNameSetting
form={form}
updateMutation={updateMutation}
handleSubmit={handleSubmit}
wording={wording}
/>
{!props.hideFields?.name && (
<ProjectNameSetting
form={form}
updateMutation={updateMutation}
handleSubmit={handleSubmit}
wording={wording}
/>
)}

<APIKeyDetails apiKey={apiKey} />

Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,106 @@
"use client";

import type { Project } from "@/api/projects";
import { SettingsCard } from "@/components/blocks/SettingsCard";
import { Input } from "@/components/ui/input";
import { useDashboardRouter } from "@/lib/DashboardRouter";
import type { ApiKey } from "@3rdweb-sdk/react/hooks/useApi";
import { useMutation } from "@tanstack/react-query";
import { useState } from "react";
import { toast } from "sonner";
import { ProjectGeneralSettingsPage } from "./ProjectGeneralSettingsPage";
import { updateProject } from "./updateProject";

export function ProjectGeneralSettingsPageForTeams(props: {
team_slug: string;
project_slug: string;
project: Project;
apiKey: ApiKey;
teamId: string;
}) {
const router = useDashboardRouter();
const { team_slug, project_slug, apiKey } = props;
const projectSettingsLayout = `/team/${team_slug}/${project_slug}/settings`;
const { team_slug, project, apiKey } = props;
const projectSettingsLayout = `/team/${team_slug}/${project.slug}/settings`;

// TODO - add a Project Image form field on this page
return (
<div className="flex flex-col gap-8">
<ProjectNameSetting
name={project.name}
update={async (name) => {
await updateProject({
projectId: project.id,
teamId: props.teamId, // TODO: remove this when project.teamId is fixed in api server
value: {
name,
},
});
router.refresh();
}}
/>

{/* TODO - replace this when we have project services endpoints */}
<ProjectGeneralSettingsPage
apiKey={apiKey}
paths={{
aaConfig: `${projectSettingsLayout}/account-abstraction`,
inAppConfig: `${projectSettingsLayout}/in-app-wallets`,
payConfig: `${projectSettingsLayout}/pay`,
afterDeleteRedirectTo: `/team/${team_slug}`,
}}
onKeyUpdated={() => {
router.refresh();
}}
wording="project"
hideFields={{
name: true,
}}
/>
</div>
);
}

function ProjectNameSetting(props: {
name: string;
update: (name: string) => Promise<void>;
}) {
const [name, setName] = useState(props.name);
const updateName = useMutation({
mutationFn: props.update,
});

const errorText = name === "" ? "Project name is required" : undefined;

return (
<ProjectGeneralSettingsPage
apiKey={apiKey}
paths={{
aaConfig: `${projectSettingsLayout}/account-abstraction`,
inAppConfig: `${projectSettingsLayout}/in-app-wallets`,
payConfig: `${projectSettingsLayout}/pay`,
afterDeleteRedirectTo: `/team/${team_slug}`,
<SettingsCard
header={{
title: "Project Name",
description:
"Assign a name to identify your project on thirdweb dashboard",
}}
onKeyUpdated={() => {
router.refresh();
noPermissionText={undefined}
errorText={errorText}
saveButton={{
onClick: () => {
const promise = updateName.mutateAsync(name);
toast.promise(promise, {
success: "Project name updated successfully",
error: "Failed to update project name",
});
},
disabled: false,
isPending: updateName.isPending,
}}
wording="project"
/>
bottomText="Please use 64 characters at maximum"
>
<Input
autoFocus
placeholder="My Project"
type="text"
value={name}
onChange={(e) => {
setName(e.target.value);
}}
className="max-w-[350px] bg-background"
/>
</SettingsCard>
);
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getProject } from "@/api/projects";
import { getTeamBySlug } from "@/api/team";
import { notFound } from "next/navigation";
import { getAPIKeyForProjectId } from "../../../../api/lib/getAPIKeys";
import { ProjectGeneralSettingsPageForTeams } from "./ProjectGeneralSettingsPageForTeams";
Expand All @@ -7,6 +8,14 @@ export default async function Page(props: {
params: { team_slug: string; project_slug: string };
}) {
const { team_slug, project_slug } = props.params;

// TODO: remove this when project.teamId is fixed in api server
const team = await getTeamBySlug(team_slug);

if (!team) {
notFound();
}

const project = await getProject(team_slug, project_slug);

if (!project) {
Expand All @@ -22,8 +31,9 @@ export default async function Page(props: {
return (
<ProjectGeneralSettingsPageForTeams
apiKey={apiKey}
project_slug={project_slug}
team_slug={team_slug}
project={project}
teamId={team.id}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"use server";

import type { Project } from "@/api/projects";
import { API_SERVER_URL } from "@/constants/env";
import { getAuthToken } from "../../../../api/lib/getAuthToken";

export async function updateProject(params: {
projectId: string;
teamId: string;
value: Partial<Project>;
}) {
const authToken = getAuthToken();

if (!authToken) {
throw new Error("No auth token");
}

const res = await fetch(
`${API_SERVER_URL}/v1/teams/${params.teamId}/projects/${params.projectId}`,
{
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${authToken}`,
},
body: JSON.stringify(params.value),
},
);

if (!res.ok) {
throw new Error("failed to update team");
}
}