-
Notifications
You must be signed in to change notification settings - Fork 544
feat: create/delete teams #7293
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
"use server"; | ||
|
||
import { randomBytes } from "node:crypto"; | ||
import type { Team } from "@/api/team"; | ||
import { format } from "date-fns"; | ||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken"; | ||
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs"; | ||
|
||
export async function createTeam(options: { | ||
name?: string; | ||
slug?: string; | ||
}): Promise< | ||
| { | ||
ok: true; | ||
data: Team; | ||
} | ||
| { | ||
ok: false; | ||
errorMessage: string; | ||
} | ||
> { | ||
const token = await getAuthToken(); | ||
|
||
if (!token) { | ||
return { | ||
ok: false, | ||
errorMessage: "You are not authorized to perform this action", | ||
}; | ||
} | ||
|
||
const res = await fetch(`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams`, { | ||
method: "POST", | ||
headers: { | ||
Authorization: `Bearer ${token}`, | ||
"Content-Type": "application/json", | ||
}, | ||
body: JSON.stringify({ | ||
name: options.name ?? `Your Projects ${format(new Date(), "MMM d yyyy")}`, | ||
slug: options.slug ?? randomBytes(20).toString("hex"), | ||
billingEmail: null, | ||
image: null, | ||
}), | ||
}); | ||
|
||
if (!res.ok) { | ||
return { | ||
ok: false, | ||
errorMessage: await res.text(), | ||
}; | ||
} | ||
|
||
const json = await res.json(); | ||
|
||
return { | ||
ok: true, | ||
data: json.result, | ||
}; | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,25 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
"use server"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { getAuthToken } from "../../app/(app)/api/lib/getAuthToken"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
import { NEXT_PUBLIC_THIRDWEB_API_HOST } from "../constants/public-envs"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
export async function deleteTeam(options: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
teamId: string; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}): Promise<void> { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const token = await getAuthToken(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!token) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
throw new Error("You are not authorized to perform this action"); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
const res = await fetch( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
{ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
method: "DELETE", | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
headers: { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Authorization: `Bearer ${token}`, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
if (!res.ok) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
throw new Error(await res.text()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Comment on lines
+13
to
+25
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainConsider enhanced error handling and security measures The API call implementation is functional but could be improved:
Consider these improvements: const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${token}`,
},
},
);
if (!res.ok) {
- throw new Error(await res.text());
+ const errorText = await res.text();
+ return {
+ ok: false,
+ errorMessage: res.status === 403
+ ? "You don't have permission to delete this team"
+ : res.status === 404
+ ? "Team not found"
+ : errorText || "Failed to delete team"
+ };
}
+ return { ok: true }; Let me verify if there are any existing permission checks for team deletion in the codebase: 🏁 Script executed: #!/bin/bash
# Search for team permission/authorization patterns
rg -A 5 -B 5 "delete.*team|team.*delete" --type ts Length of output: 9979 🏁 Script executed: #!/bin/bash
# Display contents of deleteTeam action file for context
cat apps/dashboard/src/@/actions/deleteTeam.ts Length of output: 683 Enhance error reporting in deleteTeam action The server enforces authorization and team-deletion permissions; we only need to map common failure statuses to more descriptive errors rather than return a new result type. In apps/dashboard/src/@/actions/deleteTeam.ts, update the error block as follows: const res = await fetch(
`${NEXT_PUBLIC_THIRDWEB_API_HOST}/v1/teams/${options.teamId}`,
{
method: "DELETE",
headers: {
Authorization: `Bearer ${token}`,
},
},
);
- if (!res.ok) {
- throw new Error(await res.text());
- }
+ if (!res.ok) {
+ const errorText = await res.text();
+ if (res.status === 403) {
+ throw new Error("You don’t have permission to delete this team");
+ }
+ if (res.status === 404) {
+ throw new Error("Team not found");
+ }
+ throw new Error(errorText || "Failed to delete team");
+ } • Preserves the existing 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -59,6 +59,7 @@ function Variants(props: { | |
accountAddress={accountAddressStub} | ||
connectButton={<ConnectButtonStub />} | ||
createProject={() => {}} | ||
createTeam={() => {}} | ||
account={{ | ||
id: "foo", | ||
email: "[email protected]", | ||
|
Original file line number | Diff line number | Diff line change | ||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
@@ -29,6 +29,7 @@ export type TeamHeaderCompProps = { | |||||||||
logout: () => void; | ||||||||||
connectButton: React.ReactNode; | ||||||||||
createProject: (team: Team) => void; | ||||||||||
createTeam: () => void; | ||||||||||
client: ThirdwebClient; | ||||||||||
accountAddress: string; | ||||||||||
getInboxNotifications: () => Promise<NotificationMetadata[]>; | ||||||||||
|
@@ -75,6 +76,7 @@ export function TeamHeaderDesktopUI(props: TeamHeaderCompProps) { | |||||||||
teamsAndProjects={props.teamsAndProjects} | ||||||||||
focus="team-selection" | ||||||||||
createProject={props.createProject} | ||||||||||
createTeam={props.createTeam} | ||||||||||
account={props.account} | ||||||||||
client={props.client} | ||||||||||
/> | ||||||||||
|
@@ -102,6 +104,7 @@ export function TeamHeaderDesktopUI(props: TeamHeaderCompProps) { | |||||||||
teamsAndProjects={props.teamsAndProjects} | ||||||||||
focus="project-selection" | ||||||||||
createProject={props.createProject} | ||||||||||
createTeam={props.createTeam} | ||||||||||
account={props.account} | ||||||||||
client={props.client} | ||||||||||
/> | ||||||||||
|
@@ -170,6 +173,9 @@ export function TeamHeaderMobileUI(props: TeamHeaderCompProps) { | |||||||||
upgradeTeamLink={`/team/${currentTeam.slug}/settings`} | ||||||||||
account={props.account} | ||||||||||
client={props.client} | ||||||||||
createTeam={() => { | ||||||||||
alert("createTeam"); | ||||||||||
}} | ||||||||||
Comment on lines
+176
to
+178
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Replace placeholder alert with proper createTeam implementation. The current alert implementation appears to be placeholder code. This should use the actual createTeam prop passed from the parent component for consistency with other UI components. Apply this diff to use the proper callback: - createTeam={() => {
- alert("createTeam");
- }}
+ createTeam={props.createTeam} 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||
/> | ||||||||||
</div> | ||||||||||
|
||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider input validation and improve error handling consistency
The function signature and authorization check are good, but there are a few areas for improvement:
teamId
parameter is not validated. Consider adding validation to ensure it's a non-empty string.createTeam
returns result objects withok
boolean. This inconsistency could confuse consumers.Consider aligning the error handling pattern with
createTeam
:🤖 Prompt for AI Agents