fix: address PR review - atomic upsert, MCP metadata, stale tokens, save UX

- Wrap setSecret delete+insert in a cross-DB transaction
- Add MCP metadata to all widget secret procedures and register in mcp.ts
- Show configured tokens even when no repos use that provider
- Only clear token input after mutation succeeds, with loading state
This commit is contained in:
Thomas Camlong committed 2026-06-27 10:58:35 +02:00
1 parent 2155f13f24
commit 757d59854e
3 files changed
+62 -28

No files matched your search

@@ -272,12 +272,14 @@ const ProviderTokensSection = ({ itemId, repositories }: { itemId: string; repos
const setSecret = clientApi.widget.secrets.setSecret.useMutation({ onSuccess: () => refetch() });
const deleteSecret = clientApi.widget.secrets.deleteSecret.useMutation({ onSuccess: () => refetch() });
const usedProviders = useMemo(
() => [...new Set(repositories.map((r) => r.provider).filter((p): p is ReleaseProviderKind => p !== undefined))],
[repositories],
);
const authProviders = useMemo(() => {
const usedProviders = repositories.map((r) => r.provider).filter((p): p is ReleaseProviderKind => p !== undefined);
const configuredProviders = configuredKinds.filter((k): k is ReleaseProviderKind =>
providersWithAuth.includes(k as ReleaseProviderKind),
);
return [...new Set([...usedProviders, ...configuredProviders])].filter((p) => providersWithAuth.includes(p));
}, [repositories, configuredKinds]);
const authProviders = usedProviders.filter((p) => providersWithAuth.includes(p));
if (authProviders.length === 0) return null;
return (
@@ -289,8 +291,12 @@ const ProviderTokensSection = ({ itemId, repositories }: { itemId: string; repos
key={provider}
provider={provider}
hasToken={configuredKinds.includes(provider)}
onSave={(value) => setSecret.mutate({ itemId, kind: provider, value })}
onDelete={() => deleteSecret.mutate({ itemId, kind: provider })}
onSave={async (value) => {
await setSecret.mutateAsync({ itemId, kind: provider, value });
}}
onDelete={async () => {
await deleteSecret.mutateAsync({ itemId, kind: provider });
}}
/>
))}
</Stack>
@@ -306,25 +312,35 @@ const ProviderTokenInput = ({
}: {
provider: ReleaseProviderKind;
hasToken: boolean;
onSave: (value: string) => void;
onDelete: () => void;
onSave: (value: string) => Promise<void>;
onDelete: () => Promise<void>;
}) => {
const tRepository = useScopedI18n("widget.releases.option.repositories");
const [value, setValue] = useState("");
const [editing, setEditing] = useState(false);
const [saving, setSaving] = useState(false);
const handleSave = () => {
if (value.trim()) {
onSave(value.trim());
const handleSave = async () => {
if (!value.trim()) return;
setSaving(true);
try {
await onSave(value.trim());
setValue("");
setEditing(false);
} finally {
setSaving(false);
}
};
const handleDelete = () => {
onDelete();
setValue("");
setEditing(false);
const handleDelete = async () => {
setSaving(true);
try {
await onDelete();
setValue("");
setEditing(false);
} finally {
setSaving(false);
}
};
return (
@@ -342,12 +358,12 @@ const ProviderTokenInput = ({
leftSection={<IconKey size={14} />}
/>
{editing && value.trim() && (
<Button size="xs" onClick={handleSave}>
<Button size="xs" onClick={handleSave} loading={saving}>
{tRepository("tokens.save")}
</Button>
)}
{hasToken && !editing && (
<ActionIcon variant="light" color="red" size="sm" onClick={handleDelete}>
<ActionIcon variant="light" color="red" size="sm" onClick={handleDelete} loading={saving}>
<IconTrash size={14} />
</ActionIcon>
)}