Files
skillhub/apps/web/components/BetaBanner.tsx
airano c8216dfcd2 feat(curation): auto-curation after crawl + quality messaging
Automate curation:
- Add runPostCrawlCuration() to DB queries — runs after every
  full and incremental crawl (classify, dedup, category counts)
- No more need for manual curate.mjs runs after each crawl

Quality messaging:
- Homepage stats label: "Skills" → "Curated Skills" (en/fa)
- Add curation note: "deduplicated and quality-filtered from X+ repos"
- Update fallback counts from 172K to 16K across all touchpoints
  (BetaBanner, getting-started prompts, email templates)
- getting-started skill count query now uses browse-ready filter

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 15:06:18 +03:30

63 lines
1.9 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect } from 'react';
import { X, Zap } from 'lucide-react';
import { useLocale, useTranslations } from 'next-intl';
import Link from 'next/link';
import { formatCompactNumber } from '@/lib/format-number';
export function BetaBanner() {
const [isVisible, setIsVisible] = useState(false);
const [skillCount, setSkillCount] = useState<string | null>(null);
const locale = useLocale();
const t = useTranslations('banner');
useEffect(() => {
const dismissed = localStorage.getItem('promo-banner-dismissed');
if (!dismissed) {
setIsVisible(true);
}
fetch('/api/stats')
.then((res) => res.json())
.then((data) => {
if (data.totalSkills) {
setSkillCount(formatCompactNumber(data.totalSkills, locale));
}
})
.catch(() => {});
}, [locale]);
const handleDismiss = () => {
setIsVisible(false);
localStorage.setItem('promo-banner-dismissed', 'true');
};
if (!isVisible) return null;
const count = skillCount || (locale === 'fa' ? '۱۶k' : '16k');
return (
<div className="relative bg-gradient-to-r from-primary-600 to-primary-500 text-white">
<div className="container-main py-2 px-4 flex items-center justify-center gap-2 text-sm">
<Zap className="w-4 h-4 flex-shrink-0" />
<span className="font-medium">{t('text', { count })}</span>
<span className="hidden sm:inline text-primary-100">|</span>
<Link
href={`/${locale}/support`}
className="hidden sm:inline text-white underline underline-offset-2 hover:text-primary-100 transition-colors"
>
{t('feedback')}
</Link>
<button
onClick={handleDismiss}
className="absolute end-2 sm:end-4 p-1 hover:bg-white/10 rounded transition-colors"
aria-label={t('dismiss')}
>
<X className="w-4 h-4" />
</button>
</div>
</div>
);
}