Lin / frontend /src /components /LinkedInAccount /LinkedInCallbackHandler.jsx
Zelyanoth's picture
hn
9d384b6
raw
history blame
12.6 kB
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useLocation, useNavigate } from 'react-router-dom';
import { handleLinkedInCallback, clearLinkedInError, fetchLinkedInAccounts } from '../../store/reducers/linkedinAccountsSlice';
import apiClient from '../../services/apiClient';
const LinkedInCallbackHandler = () => {
const dispatch = useDispatch();
const location = useLocation();
const navigate = useNavigate();
const { oauthLoading, oauthError } = useSelector(state => state.linkedinAccounts);
const [status, setStatus] = useState('processing');
const [message, setMessage] = useState('Processing LinkedIn authentication...');
useEffect(() => {
const handleCallback = async () => {
try {
// Parse URL parameters
const urlParams = new URLSearchParams(location.search);
const code = urlParams.get('code');
const state = urlParams.get('state');
const error = urlParams.get('error');
const oauthSuccess = urlParams.get('oauth_success');
const from = urlParams.get('from');
// DEBUG: Log callback parameters
console.log('πŸ”— [Frontend] LinkedIn callback handler started');
console.log('πŸ”— [Frontend] URL parameters:', {
code: code?.substring(0, 20) + '...',
state,
error,
oauthSuccess,
from
});
// Check if we're coming from LinkedIn OAuth
if (from === 'linkedin') {
if (error) {
console.error('πŸ”— [Frontend] OAuth error:', error);
setStatus('error');
setMessage(`Authentication failed: ${error}`);
return;
}
if (oauthSuccess === 'true') {
console.log('πŸ”— [Frontend] OAuth success detected, fetching session data...');
// Get OAuth data from backend session
try {
const sessionResponse = await apiClient.get('/accounts/session-data');
console.log('πŸ”— [Frontend] Session response:', sessionResponse);
if (sessionResponse.data.success && sessionResponse.data.oauth_data) {
const oauthData = sessionResponse.data.oauth_data;
console.log('πŸ”— [Frontend] OAuth data from backend session:', oauthData);
setStatus('processing');
setMessage('Completing LinkedIn authentication...');
// Make the API call to complete the OAuth flow
const response = await apiClient.post('/accounts/callback', {
code: oauthData.code,
state: oauthData.state,
social_network: 'LinkedIn'
});
// DEBUG: Log callback response
console.log('πŸ”— [Frontend] API response received:', response);
console.log('πŸ”— [Frontend] Response status:', response.status);
console.log('πŸ”— [Frontend] Response data:', response.data);
if (response.data.success) {
console.log('πŸ”— [Frontend] LinkedIn account linked successfully!');
setStatus('success');
setMessage('LinkedIn account linked successfully!');
// Dispatch success action to update Redux state
await dispatch(fetchLinkedInAccounts());
// Redirect to sources page after a short delay
setTimeout(() => {
console.log('πŸ”— [Frontend] Redirecting to sources page...');
navigate('/sources');
}, 2000);
} else {
console.error('πŸ”— [Frontend] LinkedIn account linking failed:', response.data.message);
setStatus('error');
setMessage(response.data.message || 'Failed to link LinkedIn account');
}
} else {
console.error('πŸ”— [Frontend] No OAuth data found in backend session');
setStatus('error');
setMessage('No authentication data found. Please try again.');
}
} catch (sessionError) {
console.error('πŸ”— [Frontend] Failed to get session data:', sessionError);
setStatus('error');
setMessage('Failed to retrieve authentication data. Please try again.');
}
return;
}
}
// Fallback to original URL parameter handling
if (error) {
console.error('πŸ”— [Frontend] OAuth error:', error);
setStatus('error');
setMessage(`Authentication failed: ${error}`);
return;
}
if (!code || !state) {
console.log('πŸ”— [Frontend] No OAuth parameters found, checking if this is a normal page load');
// This might be a normal page load, not an OAuth callback
setStatus('error');
setMessage('No authentication data found. Please try again.');
return;
}
setStatus('processing');
setMessage('Completing LinkedIn authentication...');
// DEBUG: Log callback processing
console.log('πŸ”— [Frontend] Processing OAuth callback...');
console.log('πŸ”— [Frontend] Making API call to complete OAuth flow...');
// Make the API call to complete the OAuth flow
const response = await apiClient.post('/accounts/callback', {
code: code,
state: state,
social_network: 'LinkedIn'
});
// DEBUG: Log callback response
console.log('πŸ”— [Frontend] API response received:', response);
console.log('πŸ”— [Frontend] Response status:', response.status);
console.log('πŸ”— [Frontend] Response data:', response.data);
if (response.data.success) {
console.log('πŸ”— [Frontend] LinkedIn account linked successfully!');
setStatus('success');
setMessage('LinkedIn account linked successfully!');
// Dispatch success action to update Redux state
await dispatch(fetchLinkedInAccounts());
// Redirect to sources page after a short delay
setTimeout(() => {
console.log('πŸ”— [Frontend] Redirecting to sources page...');
navigate('/sources');
}, 2000);
} else {
console.error('πŸ”— [Frontend] LinkedIn account linking failed:', response.data.message);
setStatus('error');
setMessage(response.data.message || 'Failed to link LinkedIn account');
}
} catch (error) {
console.error('πŸ”— [Frontend] Callback handler error:', error);
console.error('πŸ”— [Frontend] Error details:', {
message: error.message,
response: error.response?.data,
status: error.response?.status
});
setStatus('error');
setMessage('An error occurred during authentication');
}
};
// Check if we're on the callback URL
if (location.search.includes('code=') || location.search.includes('error=') || location.search.includes('oauth_success=')) {
console.log('πŸ”— [Frontend] Detected callback URL, processing...');
handleCallback();
} else {
console.log('πŸ”— [Frontend] Not a callback URL, normal page load');
}
}, [dispatch, location, navigate]);
const handleRetry = () => {
dispatch(clearLinkedInError());
navigate('/sources');
};
return (
<div className="linkedin-callback-handler">
<div className="callback-container">
<div className="callback-content">
{status === 'processing' && (
<div className="processing-state">
<div className="spinner"></div>
<h2>{message}</h2>
<p>Please wait while we complete the authentication process...</p>
</div>
)}
{status === 'success' && (
<div className="success-state">
<div className="success-icon">βœ“</div>
<h2>{message}</h2>
<p>Redirecting to your accounts...</p>
</div>
)}
{status === 'error' && (
<div className="error-state">
<div className="error-icon">βœ—</div>
<h2>{message}</h2>
<p>There was an issue with the LinkedIn authentication process.</p>
<div className="error-actions">
<button onClick={handleRetry} className="btn btn-primary">
Try Again
</button>
<button onClick={() => navigate('/sources')} className="btn btn-secondary">
Go to Sources
</button>
</div>
</div>
)}
</div>
</div>
<style jsx>{`
.linkedin-callback-handler {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
padding: 20px;
}
.callback-container {
background: white;
border-radius: 12px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
padding: 40px;
max-width: 500px;
width: 100%;
text-align: center;
}
.callback-content {
min-height: 200px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.processing-state,
.success-state,
.error-state {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.spinner {
width: 48px;
height: 48px;
border: 4px solid #f3f3f3;
border-top: 4px solid #910029;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 24px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.success-icon {
width: 64px;
height: 64px;
background: #28a745;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 32px;
font-weight: bold;
margin-bottom: 24px;
}
.error-icon {
width: 64px;
height: 64px;
background: #dc3545;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 32px;
font-weight: bold;
margin-bottom: 24px;
}
.processing-state h2,
.success-state h2,
.error-state h2 {
margin: 0 0 16px 0;
font-size: 24px;
font-weight: 600;
color: #333;
}
.processing-state p,
.success-state p,
.error-state p {
margin: 0 0 24px 0;
color: #666;
font-size: 16px;
line-height: 1.5;
}
.error-actions {
display: flex;
gap: 12px;
justify-content: center;
}
.btn {
padding: 12px 24px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
text-decoration: none;
display: inline-block;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background-color: #910029;
color: white;
}
.btn-primary:hover:not(:disabled) {
background-color: #7a0023;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
.btn-secondary:hover:not(:disabled) {
background-color: #5a6268;
}
@media (max-width: 600px) {
.callback-container {
padding: 24px;
margin: 10px;
}
.error-actions {
flex-direction: column;
width: 100%;
}
.btn {
width: 100%;
}
}
`}</style>
</div>
);
};
export default LinkedInCallbackHandler;