bugfix(CON-01, CON-02)

This commit is contained in:
Ida 2026-04-17 13:54:00 +02:00
parent f09f43666a
commit 238dd6ae16
2 changed files with 43 additions and 12 deletions

View file

@ -333,6 +333,8 @@ export function useConnections() {
// Create Google connection and open OAuth popup
const createGoogleConnectionAndAuth = async (): Promise<void> => {
if (isConnecting) return;
setIsConnecting(true);
try {
// Step 1: Create a Google connection
const newConnection = await createConnection({
@ -354,7 +356,7 @@ export function useConnections() {
authUrl = `${apiBaseUrl}${authUrl}`;
}
return new Promise((resolve, reject) => {
return await new Promise<void>((resolve, reject) => {
const popup = window.open(
authUrl,
'google-connection',
@ -362,6 +364,7 @@ export function useConnections() {
);
if (!popup) {
setIsConnecting(false);
reject(new Error('Popup was blocked. Please allow popups and try again.'));
return;
}
@ -371,6 +374,7 @@ export function useConnections() {
if (popup.closed) {
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
setIsConnecting(false);
console.log('Google OAuth popup closed');
// Refresh connections in case it succeeded
fetchConnections();
@ -390,6 +394,7 @@ export function useConnections() {
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
popup.close();
setIsConnecting(false);
console.log('Google connection successful');
// Refresh connections
fetchConnections();
@ -398,6 +403,7 @@ export function useConnections() {
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
popup.close();
setIsConnecting(false);
reject(new Error(event.data.error || 'Google connection failed'));
}
};
@ -405,6 +411,7 @@ export function useConnections() {
window.addEventListener('message', messageListener);
});
} catch (error) {
setIsConnecting(false);
console.error('Error creating Google connection:', error);
throw error;
}
@ -412,6 +419,8 @@ export function useConnections() {
// Create ClickUp connection and open OAuth popup
const createClickupConnectionAndAuth = async (): Promise<void> => {
if (isConnecting) return;
setIsConnecting(true);
try {
const newConnection = await createConnection({
type: 'clickup',
@ -430,7 +439,7 @@ export function useConnections() {
authUrl = `${apiBaseUrl}${authUrl}`;
}
return new Promise((resolve, reject) => {
return await new Promise<void>((resolve, reject) => {
const popup = window.open(
authUrl,
'clickup-connection',
@ -438,6 +447,7 @@ export function useConnections() {
);
if (!popup) {
setIsConnecting(false);
reject(new Error('Popup was blocked. Please allow popups and try again.'));
return;
}
@ -446,6 +456,7 @@ export function useConnections() {
if (popup.closed) {
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
setIsConnecting(false);
console.log('ClickUp OAuth popup closed');
fetchConnections();
resolve();
@ -462,6 +473,7 @@ export function useConnections() {
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
popup.close();
setIsConnecting(false);
console.log('ClickUp connection successful');
fetchConnections();
resolve();
@ -469,6 +481,7 @@ export function useConnections() {
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
popup.close();
setIsConnecting(false);
reject(new Error(event.data.error || 'ClickUp connection failed'));
}
};
@ -476,6 +489,7 @@ export function useConnections() {
window.addEventListener('message', messageListener);
});
} catch (error) {
setIsConnecting(false);
console.error('Error creating ClickUp connection:', error);
throw error;
}
@ -483,6 +497,8 @@ export function useConnections() {
// Create Microsoft connection and open OAuth popup
const createMicrosoftConnectionAndAuth = async (): Promise<void> => {
if (isConnecting) return;
setIsConnecting(true);
try {
// Step 1: Create a Microsoft connection
const newConnection = await createConnection({
@ -504,7 +520,7 @@ export function useConnections() {
authUrl = `${apiBaseUrl}${authUrl}`;
}
return new Promise((resolve, reject) => {
return await new Promise<void>((resolve, reject) => {
const popup = window.open(
authUrl,
'msft-connection',
@ -512,6 +528,7 @@ export function useConnections() {
);
if (!popup) {
setIsConnecting(false);
reject(new Error('Popup was blocked. Please allow popups and try again.'));
return;
}
@ -521,6 +538,7 @@ export function useConnections() {
if (popup.closed) {
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
setIsConnecting(false);
console.log('Microsoft OAuth popup closed');
// Refresh connections in case it succeeded
fetchConnections();
@ -540,6 +558,7 @@ export function useConnections() {
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
popup.close();
setIsConnecting(false);
console.log('Microsoft connection successful');
// Refresh connections
fetchConnections();
@ -548,6 +567,7 @@ export function useConnections() {
clearInterval(checkClosed);
window.removeEventListener('message', messageListener);
popup.close();
setIsConnecting(false);
reject(new Error(event.data.error || 'Microsoft connection failed'));
}
};
@ -555,6 +575,7 @@ export function useConnections() {
window.addEventListener('message', messageListener);
});
} catch (error) {
setIsConnecting(false);
console.error('Error creating Microsoft connection:', error);
throw error;
}

View file

@ -97,12 +97,15 @@ export const ConnectionsPage: React.FC = () => {
// Handle edit submit
const handleEditSubmit = async (data: Partial<Connection>) => {
if (!editingConnection) return;
// Note: updateConnection is handled through the hook
try {
// Ensure authority is properly typed - filter and validate authority value
const updateData: Partial<import('../../api/connectionApi').Connection> = { ...data };
// Validate and set authority if present
// Strip computed/read-only fields the backend cannot write.
delete (updateData as any).connectionReference;
delete (updateData as any).displayLabel;
delete (updateData as any).tokenStatus;
delete (updateData as any).tokenExpiresAt;
if (data.authority) {
if (
data.authority === 'local' ||
@ -112,7 +115,6 @@ export const ConnectionsPage: React.FC = () => {
) {
updateData.authority = data.authority;
} else {
// Remove invalid authority value
delete (updateData as any).authority;
}
}
@ -173,8 +175,10 @@ export const ConnectionsPage: React.FC = () => {
}
};
// Handle create Google connection
// Guards prevent double-trigger while the OAuth popup is open, which would
// otherwise create additional orphan PENDING connections on every click.
const handleCreateGoogle = async () => {
if (isConnecting) return;
try {
await createGoogleConnectionAndAuth();
refetch();
@ -183,8 +187,8 @@ export const ConnectionsPage: React.FC = () => {
}
};
// Handle create Microsoft connection
const handleCreateMicrosoft = async () => {
if (isConnecting) return;
try {
await createMicrosoftConnectionAndAuth();
refetch();
@ -194,6 +198,7 @@ export const ConnectionsPage: React.FC = () => {
};
const handleCreateClickup = async () => {
if (isConnecting) return;
try {
await createClickupConnectionAndAuth();
refetch();
@ -222,7 +227,12 @@ export const ConnectionsPage: React.FC = () => {
// Form attributes for edit modal
const formAttributes = useMemo(() => {
const excludedFields = ['id', 'mandateId', 'userId', 'sysCreatedBy', 'sysCreatedAt', 'sysModifiedAt', 'connectedAt', 'lastChecked'];
const excludedFields = [
'id', 'mandateId', 'userId', 'sysCreatedBy', 'sysCreatedAt', 'sysModifiedAt',
'connectedAt', 'lastChecked',
// computed/read-only fields the backend rejects on write
'connectionReference', 'displayLabel', 'tokenStatus', 'tokenExpiresAt',
];
return (attributes || [])
.filter(attr => !excludedFields.includes(attr.name));
}, [attributes]);
@ -255,7 +265,7 @@ export const ConnectionsPage: React.FC = () => {
className={styles.secondaryButton}
onClick={handleAdminConsent}
disabled={adminConsentPending}
title={t('Microsoft Admin-Zustimmung erteilt der')}
title={t('Microsoft Admin-Zustimmung für die gesamte Organisation erteilen')}
>
<FaShieldAlt /> {t('Admin-Zustimmung')}
</button>