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'; } from '@/settings/developers/utils/formatExpiration';
import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow'; import { TableRow } from '@/ui/layout/table/components/TableRow';
import { IconChevronRight } from 'twenty-ui/display';
import { styled } from '@linaria/react'; import { styled } from '@linaria/react';
import { useLingui } from '@lingui/react/macro';
import { useContext } from 'react'; import { useContext } from 'react';
import { IconChevronRight } from 'twenty-ui/display';
import { ThemeContext } from 'twenty-ui/theme-constants'; import { ThemeContext } from 'twenty-ui/theme-constants';
import { type ApiKey } from '~/generated-metadata/graphql'; import { type ApiKey } from '~/generated-metadata/graphql';
@@ -29,6 +30,7 @@ export const SettingsApiKeysFieldItemTableRow = ({
apiKey, apiKey,
to, to,
}: SettingsApiKeysFieldItemTableRowProps) => { }: SettingsApiKeysFieldItemTableRowProps) => {
const { t } = useLingui();
const { theme } = useContext(ThemeContext); const { theme } = useContext(ThemeContext);
const formattedExpiration = formatExpiration(apiKey.expiresAt || null); const formattedExpiration = formatExpiration(apiKey.expiresAt || null);
@@ -43,7 +45,9 @@ export const SettingsApiKeysFieldItemTableRow = ({
textOverflow="ellipsis" textOverflow="ellipsis"
clickable clickable
> >
<StyledEllipsisLabel>{apiKey.name}</StyledEllipsisLabel> <StyledEllipsisLabel>
{apiKey.name || t`Unnamed API Key`}
</StyledEllipsisLabel>
</TableCell> </TableCell>
<TableCell <TableCell
@@ -201,12 +201,18 @@ export const SettingsDevelopersApiKeyDetail = () => {
const regenerateApiKey = async () => { const regenerateApiKey = async () => {
setIsLoading(true); setIsLoading(true);
try { try {
if (isNonEmptyString(apiKey?.name)) { if (isDefined(apiKey)) {
if (!isNonEmptyString(apiKeyName)) {
enqueueErrorSnackBar({
message: t`API key name cannot be empty`,
});
return;
}
const newExpiresAt = computeNewExpirationDate( const newExpiresAt = computeNewExpirationDate(
apiKey?.expiresAt, apiKey.expiresAt,
apiKey?.createdAt, apiKey.createdAt,
); );
const newApiKey = await createIntegration(apiKey?.name, newExpiresAt); const newApiKey = await createIntegration(apiKeyName, newExpiresAt);
await deleteIntegration(false); await deleteIntegration(false);
if (isNonEmptyString(newApiKey?.token)) { if (isNonEmptyString(newApiKey?.token)) {
@@ -233,9 +239,9 @@ export const SettingsDevelopersApiKeyDetail = () => {
return ( return (
<> <>
{apiKey?.name && ( {isDefined(apiKey) && (
<SubMenuTopBarContainer <SubMenuTopBarContainer
title={apiKey?.name} title={apiKey.name || t`Unnamed API Key`}
links={[ links={[
{ {
children: t`Workspace`, children: t`Workspace`,
@@ -245,7 +251,7 @@ export const SettingsDevelopersApiKeyDetail = () => {
children: t`APIs & Webhooks`, children: t`APIs & Webhooks`,
href: getSettingsPath(SettingsPath.ApiWebhooks), href: getSettingsPath(SettingsPath.ApiWebhooks),
}, },
{ children: apiKey?.name }, { children: apiKey.name || t`Unnamed API Key` },
]} ]}
> >
<SettingsPageContainer> <SettingsPageContainer>
@@ -1,5 +1,5 @@
import { addDays } from 'date-fns'; 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 { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons/SaveAndCancelButtons';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer'; 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 { Select } from '@/ui/input/components/Select';
import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer'; import { SubMenuTopBarContainer } from '@/ui/layout/page/components/SubMenuTopBarContainer';
import { useMutation, useQuery } from '@apollo/client/react';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { useStore } from 'jotai'; import { useStore } from 'jotai';
import { Key } from 'ts-key-enum'; import { Key } from 'ts-key-enum';
@@ -17,7 +18,6 @@ import { SettingsPath } from 'twenty-shared/types';
import { getSettingsPath, isDefined } from 'twenty-shared/utils'; import { getSettingsPath, isDefined } from 'twenty-shared/utils';
import { H2Title } from 'twenty-ui/display'; import { H2Title } from 'twenty-ui/display';
import { Section } from 'twenty-ui/layout'; import { Section } from 'twenty-ui/layout';
import { useMutation, useQuery } from '@apollo/client/react';
import { import {
CreateApiKeyDocument, CreateApiKeyDocument,
GenerateApiKeyTokenDocument, GenerateApiKeyTokenDocument,
@@ -70,6 +70,8 @@ export const SettingsDevelopersApiKeysNew = () => {
); );
const handleSave = async () => { const handleSave = async () => {
if (!formValues.name) return;
const expiresAt = addDays( const expiresAt = addDays(
new Date(), new Date(),
formValues.expirationDate ?? 30, formValues.expirationDate ?? 30,
@@ -84,7 +86,7 @@ export const SettingsDevelopersApiKeysNew = () => {
const { data: newApiKeyData } = await createApiKey({ const { data: newApiKeyData } = await createApiKey({
variables: { variables: {
input: { input: {
name: formValues.name, name: formValues.name.trim(),
expiresAt, expiresAt,
roleId: roleIdToUse, roleId: roleIdToUse,
}, },
@@ -115,7 +117,7 @@ export const SettingsDevelopersApiKeysNew = () => {
} }
}; };
const canSave = !!formValues.name && !!formValues.roleId && createApiKey; const canSave = !!formValues.name && !!formValues.roleId;
if (rolesLoading) { if (rolesLoading) {
return <SettingsSkeletonLoader />; return <SettingsSkeletonLoader />;
@@ -137,7 +139,7 @@ export const SettingsDevelopersApiKeysNew = () => {
]} ]}
actionButton={ actionButton={
<SaveAndCancelButtons <SaveAndCancelButtons
isSaveDisabled={!isDefined(canSave)} isSaveDisabled={!canSave}
onCancel={() => { onCancel={() => {
navigateSettings(SettingsPath.ApiWebhooks); navigateSettings(SettingsPath.ApiWebhooks);
}} }}