feat: add API key editing functionality to the dashboard#358
feat: add API key editing functionality to the dashboard#358KMKoushik merged 1 commit intousesend:mainfrom
Conversation
- new edit button in /dev-settings - new updateApiKey mutation in api router - new edit dialog-component - new update-function in api-service - changed sorting of api-key query to avoid list items jumping after updates
|
@99power is attempting to deploy a commit to the kmkoushik's projects Team on Vercel. A member of the Team first needs to authorize it. |
There was a problem hiding this comment.
Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.
WalkthroughThis change introduces API key editing functionality. A new EditApiKeyDialog component is added as a client-side form to edit API key names and domain assignments. The ApiList UI is updated to include an edit button and integrate the dialog, with local state tracking the currently edited key. Backend changes add an updateApiKey tRPC router endpoint and service function that validates domain ownership and conditionally updates the name and domainId fields. Minor formatting adjustments are made to date display logic. Possibly related PRs
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/web/src/app/`(dashboard)/dev-settings/api-keys/api-list.tsx:
- Around line 80-86: The icon-only Edit button lacks an accessible label; update
the Button (the onClick handler that calls setEditingId(apiKey.id) and renders
the Edit3 icon) to include an accessible name—e.g., add an aria-label that
includes identifying context (like the API key name or id) or include
visually-hidden text next to the Edit3 icon—so screen readers can announce which
API key will be edited.
- Around line 13-15: The imports at the top of the file use relative/local paths
instead of the apps/web "~/ " alias; update the three local imports so they use
the "~/..." alias (replace ./delete-api-key and ./edit-api-key with imports from
'~/app/(dashboard)/dev-settings/api-keys/delete-api-key' and
'~/app/(dashboard)/dev-settings/api-keys/edit-api-key' or the appropriate module
paths) while leaving external library imports (Spinner from `@usesend/ui`)
unchanged; ensure symbols DeleteApiKey and EditApiKeyDialog remain correctly
referenced after switching to the alias.
In `@apps/web/src/server/service/api-service.ts`:
- Around line 96-128: The updateApiKey function can call db.apiKey.update with
an empty data object when both name and domainId are undefined; add an early
guard in updateApiKey that checks if name === undefined && domainId ===
undefined and throw a clear error (e.g., "NO_UPDATE_PAYLOAD" or descriptive
message) to avoid a no-op update, while still allowing domainId === null to be
used to clear the domain; update the logic around db.apiKey.update (referencing
updateApiKey and the db.apiKey.update call) to only proceed when there is at
least one field to change.
ℹ️ Review info
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
apps/web/src/app/(dashboard)/dev-settings/api-keys/api-list.tsxapps/web/src/app/(dashboard)/dev-settings/api-keys/edit-api-key.tsxapps/web/src/server/api/routers/api.tsapps/web/src/server/service/api-service.ts
| import DeleteApiKey from "./delete-api-key"; | ||
| import { EditApiKeyDialog } from "./edit-api-key"; | ||
| import Spinner from "@usesend/ui/src/spinner"; |
There was a problem hiding this comment.
Use the ~/ alias for local imports in apps/web.
🔧 Proposed fix
-import DeleteApiKey from "./delete-api-key";
-import { EditApiKeyDialog } from "./edit-api-key";
+import DeleteApiKey from "~/app/(dashboard)/dev-settings/api-keys/delete-api-key";
+import { EditApiKeyDialog } from "~/app/(dashboard)/dev-settings/api-keys/edit-api-key";As per coding guidelines, "Use the /alias for src imports in apps/web (e.g., import { x } from '/utils/x')."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import DeleteApiKey from "./delete-api-key"; | |
| import { EditApiKeyDialog } from "./edit-api-key"; | |
| import Spinner from "@usesend/ui/src/spinner"; | |
| import DeleteApiKey from "~/app/(dashboard)/dev-settings/api-keys/delete-api-key"; | |
| import { EditApiKeyDialog } from "~/app/(dashboard)/dev-settings/api-keys/edit-api-key"; | |
| import Spinner from "@usesend/ui/src/spinner"; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/app/`(dashboard)/dev-settings/api-keys/api-list.tsx around lines
13 - 15, The imports at the top of the file use relative/local paths instead of
the apps/web "~/ " alias; update the three local imports so they use the "~/..."
alias (replace ./delete-api-key and ./edit-api-key with imports from
'~/app/(dashboard)/dev-settings/api-keys/delete-api-key' and
'~/app/(dashboard)/dev-settings/api-keys/edit-api-key' or the appropriate module
paths) while leaving external library imports (Spinner from `@usesend/ui`)
unchanged; ensure symbols DeleteApiKey and EditApiKeyDialog remain correctly
referenced after switching to the alias.
| <Button | ||
| variant="ghost" | ||
| size="sm" | ||
| onClick={() => setEditingId(apiKey.id)} | ||
| > | ||
| <Edit3 className="h-4 w-4" /> | ||
| </Button> |
There was a problem hiding this comment.
Add an accessible label for the icon-only Edit button.
✅ Suggested fix
<Button
variant="ghost"
size="sm"
+ aria-label="Edit API key"
onClick={() => setEditingId(apiKey.id)}
>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <Button | |
| variant="ghost" | |
| size="sm" | |
| onClick={() => setEditingId(apiKey.id)} | |
| > | |
| <Edit3 className="h-4 w-4" /> | |
| </Button> | |
| <Button | |
| variant="ghost" | |
| size="sm" | |
| aria-label="Edit API key" | |
| onClick={() => setEditingId(apiKey.id)} | |
| > | |
| <Edit3 className="h-4 w-4" /> | |
| </Button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/app/`(dashboard)/dev-settings/api-keys/api-list.tsx around lines
80 - 86, The icon-only Edit button lacks an accessible label; update the Button
(the onClick handler that calls setEditingId(apiKey.id) and renders the Edit3
icon) to include an accessible name—e.g., add an aria-label that includes
identifying context (like the API key name or id) or include visually-hidden
text next to the Edit3 icon—so screen readers can announce which API key will be
edited.
| export async function updateApiKey({ | ||
| id, | ||
| teamId, | ||
| name, | ||
| domainId, | ||
| }: { | ||
| id: number; | ||
| teamId: number; | ||
| name?: string; | ||
| domainId?: number | null; | ||
| }) { | ||
| try { | ||
| if (domainId !== undefined && domainId !== null) { | ||
| const domain = await db.domain.findUnique({ | ||
| where: { | ||
| id: domainId, | ||
| teamId: teamId, | ||
| }, | ||
| select: { id: true }, | ||
| }); | ||
|
|
||
| if (!domain) { | ||
| throw new Error("DOMAIN_NOT_FOUND"); | ||
| } | ||
| } | ||
|
|
||
| return await db.apiKey.update({ | ||
| where: { id, teamId }, | ||
| data: { | ||
| ...(name !== undefined && { name }), | ||
| ...(domainId !== undefined && { domainId }), | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Guard against empty update payloads.
If name and domainId are both undefined, the update becomes a no-op and can surface a confusing error downstream. Add an early guard with a clear message.
🛠️ Proposed fix
try {
+ if (name === undefined && domainId === undefined) {
+ throw new Error("NO_UPDATE_FIELDS");
+ }
+
if (domainId !== undefined && domainId !== null) {
const domain = await db.domain.findUnique({📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export async function updateApiKey({ | |
| id, | |
| teamId, | |
| name, | |
| domainId, | |
| }: { | |
| id: number; | |
| teamId: number; | |
| name?: string; | |
| domainId?: number | null; | |
| }) { | |
| try { | |
| if (domainId !== undefined && domainId !== null) { | |
| const domain = await db.domain.findUnique({ | |
| where: { | |
| id: domainId, | |
| teamId: teamId, | |
| }, | |
| select: { id: true }, | |
| }); | |
| if (!domain) { | |
| throw new Error("DOMAIN_NOT_FOUND"); | |
| } | |
| } | |
| return await db.apiKey.update({ | |
| where: { id, teamId }, | |
| data: { | |
| ...(name !== undefined && { name }), | |
| ...(domainId !== undefined && { domainId }), | |
| }, | |
| }); | |
| export async function updateApiKey({ | |
| id, | |
| teamId, | |
| name, | |
| domainId, | |
| }: { | |
| id: number; | |
| teamId: number; | |
| name?: string; | |
| domainId?: number | null; | |
| }) { | |
| try { | |
| if (name === undefined && domainId === undefined) { | |
| throw new Error("NO_UPDATE_FIELDS"); | |
| } | |
| if (domainId !== undefined && domainId !== null) { | |
| const domain = await db.domain.findUnique({ | |
| where: { | |
| id: domainId, | |
| teamId: teamId, | |
| }, | |
| select: { id: true }, | |
| }); | |
| if (!domain) { | |
| throw new Error("DOMAIN_NOT_FOUND"); | |
| } | |
| } | |
| return await db.apiKey.update({ | |
| where: { id, teamId }, | |
| data: { | |
| ...(name !== undefined && { name }), | |
| ...(domainId !== undefined && { domainId }), | |
| }, | |
| }); |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/web/src/server/service/api-service.ts` around lines 96 - 128, The
updateApiKey function can call db.apiKey.update with an empty data object when
both name and domainId are undefined; add an early guard in updateApiKey that
checks if name === undefined && domainId === undefined and throw a clear error
(e.g., "NO_UPDATE_PAYLOAD" or descriptive message) to avoid a no-op update,
while still allowing domainId === null to be used to clear the domain; update
the logic around db.apiKey.update (referencing updateApiKey and the
db.apiKey.update call) to only proceed when there is at least one field to
change.
Once API keys have been created, we can't change neither name nor scope. In order to avoid rolling/recreating of a key I thought it'd be useful to edit scope/name.
Summary by cubic
Added API key editing in the dashboard to rename keys and change domain access without regenerating keys.
New Features
Bug Fixes
Written for commit dbb416c. Summary will update on new commits.
Summary by CodeRabbit