fix: prevent saving API key with empty name (#18970)

Fixes: #18959 
Generally, users need to type name before they create the api key.


https://github.com/user-attachments/assets/bb3ec0ec-d05f-48a8-b762-a35288a9e111

<img width="656" height="559" alt="image"
src="https://github.com/user-attachments/assets/cb8fd461-98f0-4475-b3f0-0de1e62624e2"
/>
This commit is contained in:
BugIsGod
2026-03-26 07:25:57 +00:00
committed by GitHub
parent 340a01567a
commit 1e2b31b040
3 changed files with 26 additions and 14 deletions
@@ -4,9 +4,10 @@ import {
} from '@/settings/developers/utils/formatExpiration';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { IconChevronRight } from 'twenty-ui/display';
import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { useContext } from 'react';
import { IconChevronRight } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme-constants';
import { type ApiKey } from '~/generated-metadata/graphql';
@@ -29,6 +30,7 @@ export const SettingsApiKeysFieldItemTableRow = ({
apiKey,
to,
}: SettingsApiKeysFieldItemTableRowProps) => {
const { t } = useLingui();
const { theme } = useContext(ThemeContext);
const formattedExpiration = formatExpiration(apiKey.expiresAt || null);
@@ -43,7 +45,9 @@ export const SettingsApiKeysFieldItemTableRow = ({
textOverflow="ellipsis"
clickable
>
<StyledEllipsisLabel>{apiKey.name}</StyledEllipsisLabel>
<StyledEllipsisLabel>
{apiKey.name || t`Unnamed API Key`}
</StyledEllipsisLabel>
</TableCell>
<TableCell
@@ -201,12 +201,18 @@ export const SettingsDevelopersApiKeyDetail = () => {
const regenerateApiKey = async () => {
setIsLoading(true);
try {
if (isNonEmptyString(apiKey?.name)) {
if (isDefined(apiKey)) {
if (!isNonEmptyString(apiKeyName)) {
enqueueErrorSnackBar({
message: t`API key name cannot be empty`,
});
return;
}
const newExpiresAt = computeNewExpirationDate(
apiKey?.expiresAt,
apiKey?.createdAt,
apiKey.expiresAt,
apiKey.createdAt,
);
const newApiKey = await createIntegration(apiKey?.name, newExpiresAt);
const newApiKey = await createIntegration(apiKeyName, newExpiresAt);
await deleteIntegration(false);
if (isNonEmptyString(newApiKey?.token)) {
@@ -233,9 +239,9 @@ export const SettingsDevelopersApiKeyDetail = () => {
return (
<>
{apiKey?.name && (
{isDefined(apiKey) && (
<SubMenuTopBarContainer
title={apiKey?.name}
title={apiKey.name || t`Unnamed API Key`}
links={[
{
children: t`Workspace`,
@@ -245,7 +251,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
children: t`APIs & Webhooks`,
href: getSettingsPath(SettingsPath.ApiWebhooks),
},
{ children: apiKey?.name },
{ children: apiKey.name || t`Unnamed API Key` },
]}
>
<SettingsPageContainer>
@@ -1,5 +1,5 @@
import { addDays } from 'date-fns';
import { useState, useCallback, useEffect } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
@@ -10,6 +10,7 @@ import { apiKeyTokenFamilyState } from '@/settings/developers/states/apiKeyToken
import { Select } from '@/ui/input/components/Select';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useMutation, useQuery } from '@apollo/client/react';
import { useLingui } from '@lingui/react/macro';
import { useStore } from 'jotai';
import { Key } from 'ts-key-enum';
@@ -17,7 +18,6 @@ import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout';
import { useMutation, useQuery } from '@apollo/client/react';
import {
CreateApiKeyDocument,
GenerateApiKeyTokenDocument,
@@ -70,6 +70,8 @@ export const SettingsDevelopersApiKeysNew = () => {
);
const handleSave = async () => {
if (!formValues.name) return;
const expiresAt = addDays(
new Date(),
formValues.expirationDate ?? 30,
@@ -84,7 +86,7 @@ export const SettingsDevelopersApiKeysNew = () => {
const { data: newApiKeyData } = await createApiKey({
variables: {
input: {
name: formValues.name,
name: formValues.name.trim(),
expiresAt,
roleId: roleIdToUse,
},
@@ -115,7 +117,7 @@ export const SettingsDevelopersApiKeysNew = () => {
}
};
const canSave = !!formValues.name && !!formValues.roleId && createApiKey;
const canSave = !!formValues.name && !!formValues.roleId;
if (rolesLoading) {
return <SettingsSkeletonLoader />;
@@ -137,7 +139,7 @@ export const SettingsDevelopersApiKeysNew = () => {
]}
actionButton={
<SaveAndCancelButtons
isSaveDisabled={!isDefined(canSave)}
isSaveDisabled={!canSave}
onCancel={() => {
navigateSettings(SettingsPath.ApiWebhooks);
}}