refactor: introduce dedicated identifier types for improved clarity and type safety

This commit is contained in:
2025-11-24 23:47:36 +01:00
parent 22f0c040a2
commit 70e7bda87e
79 changed files with 1567 additions and 1572 deletions
@@ -7,9 +7,7 @@ import {
} from 'react';
import type { JSX } from 'react';
import { CheckIcon, ChevronDownIcon } from '../../ui/icons';
type CapabilityValue = string | number;
import type { CapabilityValue } from '../../types/identifiers';
export interface CapabilityDropdownOption {
value?: CapabilityValue | null;
@@ -1,7 +1,6 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import type { ChangeEvent, FormEvent } from 'react';
type Identifier = string | number;
import type { Identifier } from '../../types/identifiers';
interface ApiTokenEntry {
id?: Identifier;
@@ -125,21 +124,21 @@ const ApiTokensSection = ({
const capabilitySelectionOptions = useMemo<CapabilitySelectionOption[]>(() => (
Array.isArray(capabilities)
? capabilities.map((capability) => {
const capabilityText = `${capability ?? ''}`;
if (!capabilityText.includes(':')) {
return { value: capability, label: capabilityText };
}
const [namespace, action] = capabilityText.split(':');
if (!namespace || !action) {
return { value: capability, label: capabilityText };
}
const formattedNamespace = `${namespace.charAt(0).toUpperCase()}${namespace.slice(1)}`;
const formattedAction = action.replace(/_/g, ' ');
return {
value: capability,
label: `${formattedNamespace}: ${formattedAction}`,
};
})
const capabilityText = `${capability ?? ''}`;
if (!capabilityText.includes(':')) {
return { value: capability, label: capabilityText };
}
const [namespace, action] = capabilityText.split(':');
if (!namespace || !action) {
return { value: capability, label: capabilityText };
}
const formattedNamespace = `${namespace.charAt(0).toUpperCase()}${namespace.slice(1)}`;
const formattedAction = action.replace(/_/g, ' ');
return {
value: capability,
label: `${formattedNamespace}: ${formattedAction}`,
};
})
: []
), [capabilities]);
@@ -8,10 +8,7 @@ import React, {
import type { SettingsSectionConfig } from '../SettingsModal';
import { IconX } from '../../ui/icons';
import CapabilityDropdown, { CapabilityDropdownOption } from '../components/CapabilityDropdown';
type CapabilityValue = string | number;
type CapabilitySetId = string | number;
import type { CapabilitySetId, CapabilityValue } from '../../types/identifiers';
interface CapabilitySet {
id: CapabilitySetId;
@@ -18,10 +18,10 @@ interface PasskeysSectionProps {
passkeysSupported?: boolean | null;
passkeysLoading?: boolean;
registeringPasskey?: boolean;
revokingPasskeyId?: string | number | null;
revokingPasskeyId?: string | null;
onRefreshPasskeys?: () => void | Promise<void>;
onRegisterPasskey?: (args: { nickname?: string }) => Promise<RegisterPasskeyResult | undefined>;
onRevokePasskey?: (id: string | number, reason?: string) => Promise<void>;
onRevokePasskey?: (id: string, reason?: string) => Promise<void>;
}
const PasskeysSection = ({
+11 -10
View File
@@ -6,6 +6,7 @@ import {
regenerateApiToken,
type ApiTokenRecord,
} from '../lib/apiClient';
import type { ApiTokenId, CapabilitySetId } from '../types/identifiers';
interface ApiTokensResponse {
token_info?: ApiTokenRecord;
@@ -15,7 +16,7 @@ interface ApiTokensResponse {
interface CreateTokenArgs {
label?: string;
expires_at?: string;
capability_set_id?: string | number;
capability_set_id?: CapabilitySetId;
}
interface UseApiTokensArgs {
@@ -28,13 +29,13 @@ interface UseApiTokensResult {
tokens: ApiTokenRecord[];
loading: boolean;
creating: boolean;
deletingId: string | number | null;
regeneratingId: string | number | null;
deletingId: ApiTokenId | null;
regeneratingId: ApiTokenId | null;
createdSecret: string | null;
refresh: () => Promise<void>;
create: (args?: CreateTokenArgs) => Promise<ApiTokensResponse | false>;
revoke: (tokenId?: string | number | null) => Promise<boolean>;
regenerate: (tokenId?: string | number | null) => Promise<boolean>;
revoke: (tokenId?: ApiTokenId | null) => Promise<boolean>;
regenerate: (tokenId?: ApiTokenId | null) => Promise<boolean>;
dismissSecret: () => void;
}
@@ -42,8 +43,8 @@ const useApiTokens = ({ notifyApiError, setStatusMessage, token }: UseApiTokensA
const [tokens, setTokens] = useState<ApiTokenRecord[]>([]);
const [loading] = useState(false);
const [creating, setCreating] = useState(false);
const [deletingId, setDeletingId] = useState<string | number | null>(null);
const [regeneratingId, setRegeneratingId] = useState<string | number | null>(null);
const [deletingId, setDeletingId] = useState<ApiTokenId | null>(null);
const [regeneratingId, setRegeneratingId] = useState<ApiTokenId | null>(null);
const [createdSecret, setCreatedSecret] = useState<string | null>(null);
const refresh = useCallback(async () => {
@@ -65,7 +66,7 @@ const useApiTokens = ({ notifyApiError, setStatusMessage, token }: UseApiTokensA
}
setCreating(true);
try {
const payload: { capability_set_id: string | number; label?: string; expires_at?: string } = { capability_set_id };
const payload: { capability_set_id: CapabilitySetId; label?: string; expires_at?: string } = { capability_set_id };
if (label) {
payload.label = label;
}
@@ -100,7 +101,7 @@ const useApiTokens = ({ notifyApiError, setStatusMessage, token }: UseApiTokensA
);
const revoke = useCallback(
async (tokenId?: string | number | null) => {
async (tokenId?: string | null) => {
if (!tokenId) {
return false;
}
@@ -121,7 +122,7 @@ const useApiTokens = ({ notifyApiError, setStatusMessage, token }: UseApiTokensA
);
const regenerate = useCallback(
async (tokenId?: string | number | null) => {
async (tokenId?: string | null) => {
if (!tokenId) {
return false;
}
+1 -2
View File
@@ -5,8 +5,7 @@ import {
listCapabilitySets,
updateCapabilitySet as updateCapabilitySetRequest,
} from '../lib/apiClient';
type Identifier = string | number;
import type { Identifier } from '../types/identifiers';
interface CapabilitySet {
id?: Identifier;
+6 -5
View File
@@ -12,6 +12,7 @@ import {
listPasskeys,
startPasskeyRegistration,
} from '../lib/apiClient';
import type { PasskeyId } from '../types/identifiers';
type StatusMessageFn = (message: string, variant?: string) => void;
type NotifyApiErrorFn = (error: unknown, message: string) => void;
@@ -27,7 +28,7 @@ type ApiError = {
};
export interface PasskeyRecord {
id?: string | number;
id?: PasskeyId;
nickname?: string;
created_at?: string;
createdAt?: string;
@@ -79,11 +80,11 @@ interface UsePasskeysResult {
passkeysSupported: boolean | null;
passkeysLoading: boolean;
registeringPasskey: boolean;
revokingPasskeyId: string | number | null;
revokingPasskeyId: PasskeyId | null;
refreshPasskeys: () => Promise<void>;
registerPasskey: (options?: { nickname?: string }) => Promise<RegisterPasskeyResult>;
revokePasskey: (
passkeyId: string | number,
passkeyId: PasskeyId,
reason?: string,
) => Promise<RevokePasskeyResult>;
}
@@ -93,7 +94,7 @@ const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArg
const [passkeysSupported, setPasskeysSupported] = useState<boolean | null>(null);
const [passkeysLoading, setPasskeysLoading] = useState(false);
const [registeringPasskey, setRegisteringPasskey] = useState(false);
const [revokingPasskeyId, setRevokingPasskeyId] = useState<string | number | null>(null);
const [revokingPasskeyId, setRevokingPasskeyId] = useState<PasskeyId | null>(null);
const refreshPasskeys = useCallback(async (): Promise<void> => {
if (!token) {
@@ -191,7 +192,7 @@ const usePasskeys = ({ notifyApiError, setStatusMessage, token }: UsePasskeysArg
const revokePasskey = useCallback(
async (
passkeyId: string | number,
passkeyId: PasskeyId,
reason?: string,
): Promise<RevokePasskeyResult> => {
if (passkeyId == null) {