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

View file

@ -97,12 +97,15 @@ export const ConnectionsPage: React.FC = () => {
// Handle edit submit // Handle edit submit
const handleEditSubmit = async (data: Partial<Connection>) => { const handleEditSubmit = async (data: Partial<Connection>) => {
if (!editingConnection) return; if (!editingConnection) return;
// Note: updateConnection is handled through the hook
try { try {
// Ensure authority is properly typed - filter and validate authority value
const updateData: Partial<import('../../api/connectionApi').Connection> = { ...data }; 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) {
if ( if (
data.authority === 'local' || data.authority === 'local' ||
@ -112,7 +115,6 @@ export const ConnectionsPage: React.FC = () => {
) { ) {
updateData.authority = data.authority; updateData.authority = data.authority;
} else { } else {
// Remove invalid authority value
delete (updateData as any).authority; 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 () => { const handleCreateGoogle = async () => {
if (isConnecting) return;
try { try {
await createGoogleConnectionAndAuth(); await createGoogleConnectionAndAuth();
refetch(); refetch();
@ -183,8 +187,8 @@ export const ConnectionsPage: React.FC = () => {
} }
}; };
// Handle create Microsoft connection
const handleCreateMicrosoft = async () => { const handleCreateMicrosoft = async () => {
if (isConnecting) return;
try { try {
await createMicrosoftConnectionAndAuth(); await createMicrosoftConnectionAndAuth();
refetch(); refetch();
@ -194,6 +198,7 @@ export const ConnectionsPage: React.FC = () => {
}; };
const handleCreateClickup = async () => { const handleCreateClickup = async () => {
if (isConnecting) return;
try { try {
await createClickupConnectionAndAuth(); await createClickupConnectionAndAuth();
refetch(); refetch();
@ -222,7 +227,12 @@ export const ConnectionsPage: React.FC = () => {
// Form attributes for edit modal // Form attributes for edit modal
const formAttributes = useMemo(() => { 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 || []) return (attributes || [])
.filter(attr => !excludedFields.includes(attr.name)); .filter(attr => !excludedFields.includes(attr.name));
}, [attributes]); }, [attributes]);
@ -255,7 +265,7 @@ export const ConnectionsPage: React.FC = () => {
className={styles.secondaryButton} className={styles.secondaryButton}
onClick={handleAdminConsent} onClick={handleAdminConsent}
disabled={adminConsentPending} disabled={adminConsentPending}
title={t('Microsoft Admin-Zustimmung erteilt der')} title={t('Microsoft Admin-Zustimmung für die gesamte Organisation erteilen')}
> >
<FaShieldAlt /> {t('Admin-Zustimmung')} <FaShieldAlt /> {t('Admin-Zustimmung')}
</button> </button>