Social Media audit: progress bar improvements

- Add detailed logging to SocialMediaAuditor (website scan, Brave search, results)
- Slow down progress bar animation (400ms instead of 200ms) for better readability
- Bold "ZNALEZIONO" text for found platforms
- Display Google rating and review count in progress
- Increase wait time before modal close (4 seconds)
- Add console.log for debugging audit response

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Maciej Pienczyn 2026-01-09 05:29:17 +01:00
parent 8fed190303
commit c319777d58
2 changed files with 330 additions and 32 deletions

View File

@ -922,6 +922,7 @@ class SocialMediaAuditor:
Returns comprehensive audit result.
"""
logger.info(f"Auditing company: {company['name']} (ID: {company['id']})")
logger.info(f"Company website: {company.get('website', 'NOT SET')}")
result = {
'company_id': company['id'],
@ -936,27 +937,43 @@ class SocialMediaAuditor:
# 1. Website audit
if company.get('website'):
try:
logger.info(f"Starting website audit for: {company['website']}")
result['website'] = self.website_auditor.audit_website(company['website'])
logger.info(f"Website audit completed. HTTP status: {result['website'].get('http_status')}")
except Exception as e:
logger.error(f"Website audit failed: {str(e)}")
result['errors'].append(f'Website audit failed: {str(e)}')
else:
logger.warning(f"No website URL for company {company['name']}")
result['website'] = {'errors': ['No website URL']}
# 2. Social media from website
website_social = result['website'].get('social_media_links', {})
if website_social:
logger.info(f"Social media found on website: {list(website_social.keys())}")
else:
logger.info("No social media links found on website")
# 3. Search for additional social media via Brave
city = company.get('address_city', 'Wejherowo')
try:
logger.info(f"Searching Brave for social media: {company['name']} in {city}")
brave_social = self.brave_searcher.search_social_media(company['name'], city)
if brave_social:
logger.info(f"Brave search found: {list(brave_social.keys())}")
else:
logger.info("Brave search found no additional social media")
# Merge, website takes precedence
for platform, url in brave_social.items():
if platform not in website_social:
website_social[platform] = url
logger.info(f"Added {platform} from Brave search: {url}")
except Exception as e:
logger.warning(f"Brave search failed: {str(e)}")
result['errors'].append(f'Brave search failed: {str(e)}')
result['social_media'] = website_social
logger.info(f"Total social media profiles found: {len(website_social)} - {list(website_social.keys())}")
# 4. Google reviews search - prefer Google Places API if available
try:

View File

@ -426,61 +426,129 @@
/* Loading Overlay */
.loading-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.7);
display: flex;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.95);
z-index: 9999;
align-items: center;
justify-content: center;
z-index: 9999;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s ease, visibility 0.3s ease;
flex-direction: column;
gap: var(--spacing-lg);
}
.loading-overlay.active {
opacity: 1;
visibility: visible;
display: flex;
}
.loading-content {
background: var(--surface);
padding: var(--spacing-xl);
border-radius: var(--radius-lg);
text-align: center;
max-width: 400px;
box-shadow: var(--shadow-lg);
max-width: 450px;
width: 90%;
text-align: center;
}
.loading-spinner {
width: 48px;
height: 48px;
border: 4px solid #e2e8f0;
.loading-header {
margin-bottom: var(--spacing-lg);
}
.loading-header h3 {
font-size: var(--font-size-lg);
color: var(--text-primary);
margin-bottom: var(--spacing-xs);
}
.loading-header p {
color: var(--text-secondary);
font-size: var(--font-size-sm);
}
.loading-steps {
text-align: left;
margin-bottom: var(--spacing-lg);
}
.loading-step {
display: flex;
align-items: center;
gap: var(--spacing-sm);
padding: var(--spacing-sm) 0;
border-bottom: 1px solid var(--border-light, #f1f5f9);
}
.loading-step:last-child {
border-bottom: none;
}
.step-icon {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.step-icon.pending { color: var(--text-tertiary); }
.step-icon.in_progress { color: #a855f7; }
.step-icon.complete { color: var(--success); }
.step-icon.error { color: var(--error); }
.step-icon.missing { color: var(--text-tertiary); opacity: 0.6; }
.step-icon.skipped { color: var(--text-tertiary); opacity: 0.5; }
.step-spinner {
width: 18px;
height: 18px;
border: 2px solid var(--border);
border-top-color: #a855f7;
border-radius: 50%;
animation: spin 1s linear infinite;
margin: 0 auto var(--spacing-md);
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.loading-title {
font-size: var(--font-size-lg);
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-sm);
}
.loading-description {
color: var(--text-secondary);
.step-text {
flex: 1;
font-size: var(--font-size-sm);
}
.step-text.pending { color: var(--text-tertiary); }
.step-text.in_progress { color: var(--text-primary); font-weight: 500; }
.step-text.complete { color: var(--text-secondary); }
.step-text.error { color: var(--error); }
.step-text.missing { color: var(--text-tertiary); font-style: italic; }
.step-text.skipped { color: var(--text-tertiary); opacity: 0.6; }
/* Source tag styling */
.source-tag {
display: inline-block;
font-size: 9px;
font-weight: 600;
padding: 1px 5px;
border-radius: 3px;
margin-left: 4px;
text-transform: uppercase;
vertical-align: middle;
}
.source-tag.website { background: #6366f1; color: white; }
.source-tag.brave { background: #fb542b; color: white; }
.source-tag.google { background: #4285f4; color: white; }
.source-tag.facebook { background: #1877f2; color: white; }
.source-tag.instagram { background: #e4405f; color: white; }
.source-tag.linkedin { background: #0a66c2; color: white; }
.source-tag.youtube { background: #ff0000; color: white; }
.source-tag.twitter { background: #000000; color: white; }
.source-tag.tiktok { background: #000000; color: white; }
/* Modal */
.modal-overlay {
position: fixed;
@ -812,9 +880,99 @@
<!-- Loading Overlay -->
<div class="loading-overlay" id="loadingOverlay">
<div class="loading-content">
<div class="loading-spinner"></div>
<div class="loading-title">Trwa audyt Social Media...</div>
<div class="loading-description">Weryfikujemy profile na platformach spolecznosciowych. Moze to potrwac kilka sekund.</div>
<div class="loading-header">
<h3>Audyt Social Media</h3>
<p>Szukam profili w mediach spolecznosciowych...</p>
</div>
<div class="loading-steps" id="loadingSteps">
<!-- Step 1: Scan Website -->
<div class="loading-step" id="step-website">
<div class="step-icon in_progress">
<div class="step-spinner"></div>
</div>
<span class="step-text in_progress">Skanuje strone WWW <span class="source-tag website">WWW</span></span>
</div>
<!-- Step 2: Facebook -->
<div class="loading-step" id="step-facebook">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Szukam Facebook <span class="source-tag facebook">FB</span></span>
</div>
<!-- Step 3: Instagram -->
<div class="loading-step" id="step-instagram">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Szukam Instagram <span class="source-tag instagram">IG</span></span>
</div>
<!-- Step 4: LinkedIn -->
<div class="loading-step" id="step-linkedin">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Szukam LinkedIn <span class="source-tag linkedin">LI</span></span>
</div>
<!-- Step 5: YouTube -->
<div class="loading-step" id="step-youtube">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Szukam YouTube <span class="source-tag youtube">YT</span></span>
</div>
<!-- Step 6: Twitter/X -->
<div class="loading-step" id="step-twitter">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Szukam X (Twitter) <span class="source-tag twitter">X</span></span>
</div>
<!-- Step 7: TikTok -->
<div class="loading-step" id="step-tiktok">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Szukam TikTok <span class="source-tag tiktok">TK</span></span>
</div>
<!-- Step 8: Google Business -->
<div class="loading-step" id="step-google">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Pobieram dane Google <span class="source-tag google">Google</span></span>
</div>
<!-- Step 9: Save -->
<div class="loading-step" id="step-save">
<div class="step-icon pending">
<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<circle cx="12" cy="12" r="10" stroke-width="2"/>
</svg>
</div>
<span class="step-text pending">Zapisuje wyniki</span>
</div>
</div>
</div>
</div>
@ -838,7 +996,70 @@
const csrfToken = '{{ csrf_token() }}';
const companySlug = '{{ company.slug }}';
// All step IDs in order
const allSteps = [
'step-website',
'step-facebook', 'step-instagram', 'step-linkedin',
'step-youtube', 'step-twitter', 'step-tiktok',
'step-google', 'step-save'
];
// Platform step mapping
const platformSteps = {
'facebook': 'step-facebook',
'instagram': 'step-instagram',
'linkedin': 'step-linkedin',
'youtube': 'step-youtube',
'twitter': 'step-twitter',
'tiktok': 'step-tiktok'
};
// SVG icons for different states
const icons = {
pending: '<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="12" r="10" stroke-width="2"/></svg>',
in_progress: '<div class="step-spinner"></div>',
complete: '<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"/></svg>',
error: '<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>',
missing: '<svg width="18" height="18" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"/></svg>'
};
function resetSteps() {
allSteps.forEach((stepId, index) => {
const stepEl = document.getElementById(stepId);
if (stepEl) {
const iconEl = stepEl.querySelector('.step-icon');
const textEl = stepEl.querySelector('.step-text');
if (index === 0) {
iconEl.className = 'step-icon in_progress';
iconEl.innerHTML = icons.in_progress;
textEl.className = 'step-text in_progress';
} else {
iconEl.className = 'step-icon pending';
iconEl.innerHTML = icons.pending;
textEl.className = 'step-text pending';
}
}
});
}
function updateStep(stepId, status, message) {
const stepEl = document.getElementById(stepId);
if (!stepEl) return;
const iconEl = stepEl.querySelector('.step-icon');
const textEl = stepEl.querySelector('.step-text');
iconEl.className = 'step-icon ' + status;
iconEl.innerHTML = icons[status] || icons.pending;
textEl.className = 'step-text ' + status;
if (message) {
textEl.innerHTML = message;
}
}
function showLoading() {
resetSteps();
document.getElementById('loadingOverlay').classList.add('active');
}
@ -867,6 +1088,46 @@ function closeModal() {
document.getElementById('modalOverlay').classList.remove('active');
}
// Animate step-by-step progress for social media platforms
async function animatePlatformSteps(foundPlatforms, googleData) {
const delay = 400; // ms between steps - slower for readability
// Website scan complete
updateStep('step-website', 'complete', 'Skanowanie strony WWW zakonczone <span class="source-tag website">WWW</span>');
await new Promise(r => setTimeout(r, delay));
// Process each platform
const platforms = ['facebook', 'instagram', 'linkedin', 'youtube', 'twitter', 'tiktok'];
for (const platform of platforms) {
const stepId = platformSteps[platform];
const platformLabel = platform === 'twitter' ? 'X (Twitter)' : platform.charAt(0).toUpperCase() + platform.slice(1);
const sourceTag = `<span class="source-tag ${platform}">${platform === 'twitter' ? 'X' : platform.substring(0,2).toUpperCase()}</span>`;
updateStep(stepId, 'in_progress', `Szukam ${platformLabel}... ${sourceTag}`);
await new Promise(r => setTimeout(r, delay / 2));
if (foundPlatforms && foundPlatforms.includes(platform)) {
updateStep(stepId, 'complete', `<strong>${platformLabel}</strong>: ZNALEZIONO ${sourceTag}`);
} else {
updateStep(stepId, 'missing', `${platformLabel}: brak profilu ${sourceTag}`);
}
await new Promise(r => setTimeout(r, delay / 2));
}
// Google data
updateStep('step-google', 'in_progress', 'Pobieram dane z Google... <span class="source-tag google">Google</span>');
await new Promise(r => setTimeout(r, delay));
if (googleData && (googleData.google_rating || googleData.google_reviews_count)) {
const rating = googleData.google_rating ? `${googleData.google_rating}/5` : '';
const reviews = googleData.google_reviews_count ? `${googleData.google_reviews_count} opinii` : '';
const details = [rating, reviews].filter(Boolean).join(', ');
updateStep('step-google', 'complete', `Google: ${details} <span class="source-tag google">Google</span>`);
} else {
updateStep('step-google', 'missing', 'Google: brak profilu GBP <span class="source-tag google">Google</span>');
}
}
async function runAudit() {
const btn = document.getElementById('runAuditBtn');
if (btn) {
@ -874,6 +1135,9 @@ async function runAudit() {
}
showLoading();
// Start animation
await new Promise(r => setTimeout(r, 300));
try {
const response = await fetch('/api/social/audit', {
method: 'POST',
@ -885,13 +1149,30 @@ async function runAudit() {
});
const data = await response.json();
console.log('Audit response:', data); // Debug
hideLoading();
// Animate the steps with found platforms and Google data
const foundPlatforms = data.platforms || [];
const googleData = data.google_reviews || {};
await animatePlatformSteps(foundPlatforms, googleData);
// Save step
updateStep('step-save', 'in_progress', 'Zapisuje wyniki do bazy...');
await new Promise(r => setTimeout(r, 400));
if (response.ok && data.success) {
showModal('Audyt zakonczony', 'Audyt Social Media zostal zakonczony pomyslnie. Strona zostanie odswiezona.', true);
setTimeout(() => location.reload(), 1500);
const summary = `Zapisano: ${data.profiles_found || 0} profili, wynik: ${data.score || 0}/100`;
updateStep('step-save', 'complete', summary);
// Wait longer for user to see complete results
await new Promise(r => setTimeout(r, 4000));
hideLoading();
showModal('Audyt zakonczony', `Znaleziono ${data.profiles_found || 0} profili social media. Strona zostanie odswiezona.`, true);
setTimeout(() => location.reload(), 2000);
} else {
updateStep('step-save', 'error', 'Blad zapisu: ' + (data.error || 'nieznany blad'));
await new Promise(r => setTimeout(r, 4000));
hideLoading();
showModal('Blad', data.error || 'Wystapil nieznany blad podczas audytu.', false);
if (btn) btn.disabled = false;
}