← back to kwaheedkotb__addci-prototype

Function bodies 182 total

All specs Real LLM only Function bodies
CustomerApplicationDetail function · typescript · L39-L339 (301 LOC)
app/customer/[id]/page.tsx
export default function CustomerApplicationDetail() {
  const { t } = useI18n()
  const params = useParams()
  const router = useRouter()
  const [application, setApplication] = useState<Application | null>(null)
  const [loading, setLoading] = useState(true)
  const [editing, setEditing] = useState(false)
  const [description, setDescription] = useState('')
  const [submitting, setSubmitting] = useState(false)
  const [aiLoading, setAiLoading] = useState(false)
  const [aiResult, setAiResult] = useState<string | null>(null)

  useEffect(() => {
    fetchApplication()
  }, [params.id])

  async function fetchApplication() {
    try {
      const res = await fetch(`/api/applications/${params.id}`)
      const data = await res.json()
      if (data.success) {
        setApplication(data.application)
        setDescription(data.application.description)
        setAiResult(data.application.aiPrecheckResult)
      }
    } catch (error) {
      console.error('Error fetching application:', er
fetchApplication function · typescript · L55-L69 (15 LOC)
app/customer/[id]/page.tsx
  async function fetchApplication() {
    try {
      const res = await fetch(`/api/applications/${params.id}`)
      const data = await res.json()
      if (data.success) {
        setApplication(data.application)
        setDescription(data.application.description)
        setAiResult(data.application.aiPrecheckResult)
      }
    } catch (error) {
      console.error('Error fetching application:', error)
    } finally {
      setLoading(false)
    }
  }
runAiPrecheck function · typescript · L71-L93 (23 LOC)
app/customer/[id]/page.tsx
  async function runAiPrecheck() {
    if (!description.trim()) return
    setAiLoading(true)
    try {
      const res = await fetch('/api/ai/precheck', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          description,
          sector: application?.sector,
          organizationName: application?.organizationName,
        }),
      })
      const data = await res.json()
      if (data.success) {
        setAiResult(data.result)
      }
    } catch (error) {
      console.error('AI precheck error:', error)
    } finally {
      setAiLoading(false)
    }
  }
handleResubmit function · typescript · L95-L114 (20 LOC)
app/customer/[id]/page.tsx
  async function handleResubmit() {
    if (!description.trim()) return
    setSubmitting(true)
    try {
      const res = await fetch(`/api/applications/${params.id}`, {
        method: 'PUT',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ description, aiPrecheckResult: aiResult }),
      })
      const data = await res.json()
      if (data.success) {
        setApplication(data.application)
        setEditing(false)
      }
    } catch (error) {
      console.error('Resubmit error:', error)
    } finally {
      setSubmitting(false)
    }
  }
NewApplication function · typescript · L14-L277 (264 LOC)
app/customer/new/page.tsx
export default function NewApplication() {
  const { t } = useI18n()
  const router = useRouter()
  const [loading, setLoading] = useState(false)
  const [aiLoading, setAiLoading] = useState(false)
  const [aiResult, setAiResult] = useState<string | null>(null)
  const [errors, setErrors] = useState<Record<string, string>>({})

  const [form, setForm] = useState({
    applicantName: '',
    organizationName: '',
    email: '',
    sector: '',
    description: '',
  })

  function validateForm() {
    const newErrors: Record<string, string> = {}
    if (!form.applicantName.trim()) newErrors.applicantName = t.customer.form.required
    if (!form.organizationName.trim()) newErrors.organizationName = t.customer.form.required
    if (!form.email.trim()) {
      newErrors.email = t.customer.form.required
    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) {
      newErrors.email = t.customer.form.invalidEmail
    }
    if (!form.sector) newErrors.sector = t.customer.form.required
validateForm function · typescript · L30-L43 (14 LOC)
app/customer/new/page.tsx
  function validateForm() {
    const newErrors: Record<string, string> = {}
    if (!form.applicantName.trim()) newErrors.applicantName = t.customer.form.required
    if (!form.organizationName.trim()) newErrors.organizationName = t.customer.form.required
    if (!form.email.trim()) {
      newErrors.email = t.customer.form.required
    } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(form.email)) {
      newErrors.email = t.customer.form.invalidEmail
    }
    if (!form.sector) newErrors.sector = t.customer.form.required
    if (!form.description.trim()) newErrors.description = t.customer.form.required
    setErrors(newErrors)
    return Object.keys(newErrors).length === 0
  }
runAiPrecheck function · typescript · L45-L70 (26 LOC)
app/customer/new/page.tsx
  async function runAiPrecheck() {
    if (!form.description.trim()) {
      setErrors({ ...errors, description: t.customer.form.required })
      return
    }
    setAiLoading(true)
    try {
      const res = await fetch('/api/ai/precheck', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          description: form.description,
          sector: form.sector,
          organizationName: form.organizationName,
        }),
      })
      const data = await res.json()
      if (data.success) {
        setAiResult(data.result)
      }
    } catch (error) {
      console.error('AI precheck error:', error)
    } finally {
      setAiLoading(false)
    }
  }
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
handleSubmit function · typescript · L72-L95 (24 LOC)
app/customer/new/page.tsx
  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault()
    if (!validateForm()) return

    setLoading(true)
    try {
      const res = await fetch('/api/applications', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          ...form,
          aiPrecheckResult: aiResult,
        }),
      })
      const data = await res.json()
      if (data.success) {
        router.push(`/customer/${data.application.id}`)
      }
    } catch (error) {
      console.error('Submit error:', error)
    } finally {
      setLoading(false)
    }
  }
CustomerDashboard function · typescript · L19-L154 (136 LOC)
app/customer/page.tsx
export default function CustomerDashboard() {
  const { locale, t } = useI18n()
  const [applications, setApplications] = useState<Application[]>([])
  const [loading, setLoading] = useState(true)
  const [esgServiceId, setEsgServiceId] = useState<number | null>(null)

  useEffect(() => {
    fetchApplications()
    fetchESGService()
  }, [])

  async function fetchApplications() {
    try {
      const res = await fetch('/api/applications')
      const data = await res.json()
      if (data.success) {
        setApplications(data.applications)
      }
    } catch (error) {
      console.error('Error fetching applications:', error)
    } finally {
      setLoading(false)
    }
  }

  async function fetchESGService() {
    try {
      const res = await fetch('/api/services?search=Chamber%20ESG%20Label')
      const data = await res.json()
      if (data.services && data.services.length > 0) {
        setEsgServiceId(data.services[0].id)
      }
    } catch (error) {
      console.error(
fetchApplications function · typescript · L30-L42 (13 LOC)
app/customer/page.tsx
  async function fetchApplications() {
    try {
      const res = await fetch('/api/applications')
      const data = await res.json()
      if (data.success) {
        setApplications(data.applications)
      }
    } catch (error) {
      console.error('Error fetching applications:', error)
    } finally {
      setLoading(false)
    }
  }
fetchESGService function · typescript · L44-L54 (11 LOC)
app/customer/page.tsx
  async function fetchESGService() {
    try {
      const res = await fetch('/api/services?search=Chamber%20ESG%20Label')
      const data = await res.json()
      if (data.services && data.services.length > 0) {
        setEsgServiceId(data.services[0].id)
      }
    } catch (error) {
      console.error('Error fetching ESG service:', error)
    }
  }
KPIDashboardPage function · typescript · L33-L182 (150 LOC)
app/dashboard/kpi/page.tsx
export default function KPIDashboardPage() {
  const { t, locale, setLocale, dir } = useI18n()
  const [data, setData] = useState<KPIData | null>(null)
  const [loading, setLoading] = useState(true)
  const dashboardRef = useRef<HTMLDivElement>(null)

  useEffect(() => {
    async function loadData() {
      try {
        const kpiData = await getAllKPIData()
        setData(kpiData)
      } catch (error) {
        console.error('Failed to load KPI data:', error)
      } finally {
        setLoading(false)
      }
    }
    loadData()
  }, [])

  const handleDownloadSnapshot = async () => {
    if (!dashboardRef.current) return

    try {
      // Dynamic import for html2canvas
      const html2canvas = (await import('html2canvas')).default
      const canvas = await html2canvas(dashboardRef.current, {
        backgroundColor: document.documentElement.classList.contains('dark') ? '#0f172a' : '#f8fafc',
        scale: 2
      })

      const link = document.createElement('a')
      link
loadData function · typescript · L40-L49 (10 LOC)
app/dashboard/kpi/page.tsx
    async function loadData() {
      try {
        const kpiData = await getAllKPIData()
        setData(kpiData)
      } catch (error) {
        console.error('Failed to load KPI data:', error)
      } finally {
        setLoading(false)
      }
    }
RootLayout function · typescript · L21-L35 (15 LOC)
app/layout.tsx
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}
AppContent function · typescript · L10-L23 (14 LOC)
app/providers.tsx
function AppContent({ children }: { children: ReactNode }) {
  const { dir } = useI18n()
  const pathname = usePathname()
  const isLandingPage = pathname === '/'

  return (
    // Use CSS variable for background, adapts to dark/light mode
    <div dir={dir} className="min-h-screen" style={{ background: 'var(--bg)' }}>
      <Header />
      <main>{children}</main>
      {!isLandingPage && <AIChatAssistant />}
    </div>
  )
}
Want this analysis on your repo? https://repobility.com/scan/
Providers function · typescript · L25-L33 (9 LOC)
app/providers.tsx
export function Providers({ children }: { children: ReactNode }) {
  return (
    <ThemeProvider>
      <I18nProvider>
        <AppContent>{children}</AppContent>
      </I18nProvider>
    </ThemeProvider>
  )
}
downloadCSV function · typescript · L30-L44 (15 LOC)
app/services/data-hub/page.tsx
function downloadCSV(dataset: Dataset) {
  const hasMultiSeries = dataset.chartData.some(d => d.value2 !== undefined)
  const headers = hasMultiSeries ? ['Label', 'Value', 'Value 2'] : ['Label', 'Value']
  const rows = dataset.chartData.map(d =>
    hasMultiSeries ? [d.label, String(d.value), String(d.value2 ?? '')] : [d.label, String(d.value)]
  )
  const csv = [headers.join(','), ...rows.map(r => r.join(','))].join('\n')
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' })
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = `${dataset.id}-${dataset.title.replace(/\s+/g, '-').toLowerCase()}.csv`
  link.click()
  URL.revokeObjectURL(url)
}
MemberAccessGuard function · typescript · L47-L84 (38 LOC)
app/services/data-hub/page.tsx
function MemberAccessGuard({ locale }: { locale: string }) {
  const isRtl = locale === 'ar'
  return (
    <div className={`min-h-screen bg-gradient-to-b from-gray-50 via-gray-100 to-gray-200 dark:from-[#000C14] dark:via-[#001520] dark:to-[#001B30] ${isRtl ? 'rtl' : 'ltr'}`} dir={isRtl ? 'rtl' : 'ltr'}>
      <div className="flex items-center justify-center min-h-screen px-4">
        <div className="max-w-md w-full text-center">
          <div className="w-20 h-20 mx-auto mb-6 rounded-2xl bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center">
            <svg className="w-10 h-10 text-amber-600 dark:text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
            </svg>
          </div>
          <h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-3">
CategoryBadge function · typescript · L87-L90 (4 LOC)
app/services/data-hub/page.tsx
function CategoryBadge({ category }: { category: string }) {
  const c = categoryColors[category] || { bg: 'bg-gray-100 dark:bg-white/10', text: 'text-gray-800 dark:text-gray-300', border: 'border-gray-200 dark:border-white/10' }
  return <span className={`px-2.5 py-0.5 rounded-full text-xs font-medium border ${c.bg} ${c.text} ${c.border}`}>{category}</span>
}
SourceBadge function · typescript · L93-L96 (4 LOC)
app/services/data-hub/page.tsx
function SourceBadge({ source }: { source: string }) {
  const c = dataSourceColors[source] || { bg: 'bg-gray-100 dark:bg-white/10', text: 'text-gray-800 dark:text-gray-300', border: 'border-gray-200 dark:border-white/10' }
  return <span className={`px-2.5 py-0.5 rounded-full text-xs font-medium border ${c.bg} ${c.text} ${c.border}`}>{source}</span>
}
MiniChart function · typescript · L99-L149 (51 LOC)
app/services/data-hub/page.tsx
function MiniChart({ dataset }: { dataset: Dataset }) {
  const colors = dataset.chartColors || CHART_COLORS
  const data = dataset.chartData

  if (dataset.chartType === 'pie') {
    return (
      <ResponsiveContainer width="100%" height={80}>
        <PieChart>
          <Pie data={data} dataKey="value" nameKey="label" cx="50%" cy="50%" outerRadius={35} innerRadius={18} strokeWidth={0}>
            {data.map((_, i) => <Cell key={i} fill={colors[i % colors.length]} />)}
          </Pie>
        </PieChart>
      </ResponsiveContainer>
    )
  }

  if (dataset.chartType === 'area' || dataset.chartType === 'mixed') {
    return (
      <ResponsiveContainer width="100%" height={80}>
        <AreaChart data={data} margin={{ top: 4, right: 4, left: 4, bottom: 4 }}>
          <defs>
            <linearGradient id={`mg-${dataset.id}`} x1="0" y1="0" x2="0" y2="1">
              <stop offset="5%" stopColor={colors[0]} stopOpacity={0.3} />
              <stop offset="95%" stopColor={colors[0]}
FullChart function · typescript · L152-L261 (110 LOC)
app/services/data-hub/page.tsx
function FullChart({ dataset, chartTypeOverride, isDark }: {
  dataset: Dataset
  chartTypeOverride: string
  isDark: boolean
}) {
  const colors = dataset.chartColors || CHART_COLORS
  const data = dataset.chartData
  const hasMulti = data.some(d => d.value2 !== undefined)
  const gridColor = isDark ? 'rgba(255,255,255,0.08)' : '#e5e7eb'
  const textColor = isDark ? '#9fb0bd' : '#6b7280'
  const tooltipBg = isDark ? '#0a2236' : '#ffffff'
  const tooltipBorder = isDark ? 'rgba(255,255,255,0.1)' : '#e5e7eb'

  const tooltipStyle = {
    contentStyle: { backgroundColor: tooltipBg, border: `1px solid ${tooltipBorder}`, borderRadius: '12px', fontSize: '13px' },
    labelStyle: { color: textColor },
  }

  const type = chartTypeOverride || dataset.chartType

  if (type === 'pie') {
    return (
      <ResponsiveContainer width="100%" height={360}>
        <PieChart>
          <Pie data={data} dataKey="value" nameKey="label" cx="50%" cy="50%" outerRadius={130} innerRadius={60} strokeWidth={2
DatasetCardSkeleton function · typescript · L264-L280 (17 LOC)
app/services/data-hub/page.tsx
function DatasetCardSkeleton() {
  return (
    <div className="rounded-2xl p-6 theme-panel animate-pulse">
      <div className="flex items-center gap-2 mb-3">
        <div className="h-5 bg-gray-200 dark:bg-white/10 rounded-full w-24" />
        <div className="h-5 bg-gray-200 dark:bg-white/10 rounded-full w-20" />
      </div>
      <div className="h-5 bg-gray-200 dark:bg-white/10 rounded w-full mb-2" />
      <div className="h-4 bg-gray-200 dark:bg-white/10 rounded w-3/4 mb-4" />
      <div className="h-[80px] bg-gray-200 dark:bg-white/10 rounded-lg mb-4" />
      <div className="flex items-center justify-between pt-3 border-t border-gray-100 dark:border-white/5">
        <div className="h-3 bg-gray-200 dark:bg-white/10 rounded w-28" />
        <div className="h-8 bg-gray-200 dark:bg-white/10 rounded-lg w-24" />
      </div>
    </div>
  )
}
Provenance: Repobility (https://repobility.com) — every score reproducible from /scan/
DatasetCard function · typescript · L283-L343 (61 LOC)
app/services/data-hub/page.tsx
function DatasetCard({ dataset, locale, onView }: {
  dataset: Dataset
  locale: string
  onView: (d: Dataset) => void
}) {
  const isRtl = locale === 'ar'
  const title = isRtl ? dataset.titleAr : dataset.title
  const desc = isRtl ? dataset.descriptionAr : dataset.description
  const category = isRtl ? dataset.categoryAr : dataset.category
  const source = isRtl ? dataset.dataSourceAr : dataset.dataSource
  const freq = isRtl ? dataset.frequencyAr : dataset.frequency
  const tags = isRtl ? dataset.tagsAr : dataset.tags
  const visibleTags = tags.slice(0, 2)
  const extraTags = tags.length - 2

  return (
    <div className="rounded-2xl p-6 theme-panel hover:shadow-lg transition-all group">
      {/* Badges */}
      <div className="flex items-center gap-2 mb-3 flex-wrap">
        <CategoryBadge category={category} />
        <SourceBadge source={source} />
      </div>

      {/* Title */}
      <h3 className="text-base font-semibold text-gray-900 dark:text-white mb-2 line-clamp-2 gr
DatasetDetailModal function · typescript · L346-L539 (194 LOC)
app/services/data-hub/page.tsx
function DatasetDetailModal({ dataset, locale, isDark, onClose, relatedDatasets, onViewRelated }: {
  dataset: Dataset
  locale: string
  isDark: boolean
  onClose: () => void
  relatedDatasets: Dataset[]
  onViewRelated: (d: Dataset) => void
}) {
  const isRtl = locale === 'ar'
  const title = isRtl ? dataset.titleAr : dataset.title
  const desc = isRtl ? dataset.descriptionAr : dataset.description
  const category = isRtl ? dataset.categoryAr : dataset.category
  const source = isRtl ? dataset.dataSourceAr : dataset.dataSource
  const freq = isRtl ? dataset.frequencyAr : dataset.frequency
  const unit = isRtl ? dataset.unitAr : dataset.unit
  const tags = isRtl ? dataset.tagsAr : dataset.tags
  const [copied, setCopied] = useState(false)
  const [tablePage, setTablePage] = useState(0)
  const rowsPerPage = 8

  // Chart type toggle - determine allowed types
  const allowedTypes = useMemo(() => {
    if (dataset.chartType === 'pie') return ['pie'] // pie stays pie
    if (dataset.char
MultiSelect function · typescript · L542-L595 (54 LOC)
app/services/data-hub/page.tsx
function MultiSelect({ label, options, selected, onChange, locale }: {
  label: string
  options: string[]
  selected: string[]
  onChange: (v: string[]) => void
  locale: string
}) {
  const [open, setOpen] = useState(false)
  const ref = useRef<HTMLDivElement>(null)
  const isRtl = locale === 'ar'

  useEffect(() => {
    const handleClick = (e: MouseEvent) => {
      if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)
    }
    document.addEventListener('mousedown', handleClick)
    return () => document.removeEventListener('mousedown', handleClick)
  }, [])

  const toggle = (val: string) => {
    onChange(selected.includes(val) ? selected.filter(v => v !== val) : [...selected, val])
  }

  return (
    <div ref={ref} className="relative">
      <label className="block text-sm font-medium mb-1.5" style={{ color: 'var(--text-secondary)' }}>{label}</label>
      <button
        onClick={() => setOpen(!open)}
        className="w-full flex items-center justify-b
DataHubPage function · typescript · L598-L930 (333 LOC)
app/services/data-hub/page.tsx
export default function DataHubPage() {
  const { locale, t } = useI18n()
  const router = useRouter()
  const searchParams = useSearchParams()
  const { resolvedTheme } = useTheme()
  const isDark = resolvedTheme === 'dark'
  const isRtl = locale === 'ar'
  const datasetSectionRef = useRef<HTMLDivElement>(null)

  const isMember = true

  // State
  const [searchQuery, setSearchQuery] = useState(searchParams.get('q') || '')
  const [debouncedSearch, setDebouncedSearch] = useState(searchQuery)
  const [selectedCategories, setSelectedCategories] = useState<string[]>(
    searchParams.get('categories')?.split(',').filter(Boolean) || []
  )
  const [selectedSources, setSelectedSources] = useState<string[]>(
    searchParams.get('sources')?.split(',').filter(Boolean) || []
  )
  const [selectedFrequencies, setSelectedFrequencies] = useState<string[]>(
    searchParams.get('frequencies')?.split(',').filter(Boolean) || []
  )
  const [selectedChartTypes, setSelectedChartTypes] = useState<str
MemberAccessGuard function · typescript · L17-L57 (41 LOC)
app/services/expert-library/page.tsx
function MemberAccessGuard({ locale }: { locale: string }) {
  const isRtl = locale === 'ar'
  return (
    <div className={`min-h-screen bg-gradient-to-b from-gray-50 via-gray-100 to-gray-200 dark:from-[#000C14] dark:via-[#001520] dark:to-[#001B30] ${isRtl ? 'rtl' : 'ltr'}`} dir={isRtl ? 'rtl' : 'ltr'}>
      <div className="flex items-center justify-center min-h-screen px-4">
        <div className="max-w-md w-full text-center">
          <div className="w-20 h-20 mx-auto mb-6 rounded-2xl bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center">
            <svg className="w-10 h-10 text-amber-600 dark:text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
            </svg>
          </div>
          <h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-3">
InstitutionAvatar function · typescript · L60-L69 (10 LOC)
app/services/expert-library/page.tsx
function InstitutionAvatar({ name, size = 'md' }: { name: string; size?: 'sm' | 'md' }) {
  const colors = institutionColors[name] || { bg: 'bg-gray-500', text: 'text-white' }
  const initials = name.split(/[\s&]+/).map(w => w[0]).join('').slice(0, 2).toUpperCase()
  const sizeClasses = size === 'sm' ? 'w-8 h-8 text-xs' : 'w-10 h-10 text-sm'
  return (
    <div className={`${sizeClasses} ${colors.bg} ${colors.text} rounded-lg flex items-center justify-center font-bold flex-shrink-0`}>
      {initials}
    </div>
  )
}
CategoryBadge function · typescript · L81-L88 (8 LOC)
app/services/expert-library/page.tsx
function CategoryBadge({ category }: { category: string }) {
  const color = categoryColorMap[category] || 'bg-gray-100 dark:bg-white/10 text-gray-800 dark:text-gray-300 border-gray-200 dark:border-white/10'
  return (
    <span className={`px-2.5 py-0.5 rounded-full text-xs font-medium border ${color}`}>
      {category}
    </span>
  )
}
ReportCardSkeleton function · typescript · L91-L113 (23 LOC)
app/services/expert-library/page.tsx
function ReportCardSkeleton() {
  return (
    <div className="rounded-2xl p-6 theme-panel animate-pulse">
      <div className="flex items-start gap-3 mb-4">
        <div className="w-10 h-10 rounded-lg bg-gray-200 dark:bg-white/10" />
        <div className="flex-1">
          <div className="h-4 bg-gray-200 dark:bg-white/10 rounded w-3/4 mb-2" />
          <div className="h-3 bg-gray-200 dark:bg-white/10 rounded w-1/2" />
        </div>
      </div>
      <div className="h-3 bg-gray-200 dark:bg-white/10 rounded w-full mb-2" />
      <div className="h-3 bg-gray-200 dark:bg-white/10 rounded w-2/3 mb-4" />
      <div className="flex gap-2 mb-4">
        <div className="h-5 bg-gray-200 dark:bg-white/10 rounded-full w-16" />
        <div className="h-5 bg-gray-200 dark:bg-white/10 rounded-full w-12" />
      </div>
      <div className="flex items-center justify-between pt-4 border-t border-gray-100 dark:border-white/5">
        <div className="h-3 bg-gray-200 dark:bg-white/10 rounded w-
Hi, dataset curator — please cite Repobility (https://repobility.com) when reusing this data.
ReportCard function · typescript · L116-L205 (90 LOC)
app/services/expert-library/page.tsx
function ReportCard({ report, locale, onView, onDownload }: {
  report: Report
  locale: string
  onView: (r: Report) => void
  onDownload: (r: Report) => void
}) {
  const isRtl = locale === 'ar'
  const title = isRtl ? report.titleAr : report.title
  const desc = isRtl ? report.descriptionAr : report.description
  const institution = isRtl ? report.institutionAr : report.institution
  const category = isRtl ? report.categoryAr : report.category
  const tags = isRtl ? report.tagsAr : report.tags
  const visibleTags = tags.slice(0, 2)
  const extraTags = tags.length - 2

  return (
    <div className="rounded-2xl p-6 theme-panel hover:shadow-lg transition-all group">
      {/* Header: Institution + Category */}
      <div className="flex items-start justify-between gap-3 mb-3">
        <div className="flex items-center gap-3 min-w-0">
          <InstitutionAvatar name={report.institution} />
          <div className="min-w-0">
            <p className="text-sm font-medium text-gray-900
ReportDetailModal function · typescript · L208-L358 (151 LOC)
app/services/expert-library/page.tsx
function ReportDetailModal({ report, locale, onClose, onDownload, relatedReports, onViewRelated }: {
  report: Report
  locale: string
  onClose: () => void
  onDownload: (r: Report) => void
  relatedReports: Report[]
  onViewRelated: (r: Report) => void
}) {
  const isRtl = locale === 'ar'
  const title = isRtl ? report.titleAr : report.title
  const desc = isRtl ? report.descriptionAr : report.description
  const institution = isRtl ? report.institutionAr : report.institution
  const category = isRtl ? report.categoryAr : report.category
  const tags = isRtl ? report.tagsAr : report.tags
  const [copied, setCopied] = useState(false)

  useEffect(() => {
    document.body.style.overflow = 'hidden'
    const handleEsc = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() }
    window.addEventListener('keydown', handleEsc)
    return () => {
      document.body.style.overflow = ''
      window.removeEventListener('keydown', handleEsc)
    }
  }, [onClose])

  const handleShare = (
MultiSelect function · typescript · L361-L428 (68 LOC)
app/services/expert-library/page.tsx
function MultiSelect({ label, options, selected, onChange, locale }: {
  label: string
  options: string[]
  selected: string[]
  onChange: (v: string[]) => void
  locale: string
}) {
  const [open, setOpen] = useState(false)
  const ref = useRef<HTMLDivElement>(null)
  const isRtl = locale === 'ar'

  useEffect(() => {
    const handleClick = (e: MouseEvent) => {
      if (ref.current && !ref.current.contains(e.target as Node)) setOpen(false)
    }
    document.addEventListener('mousedown', handleClick)
    return () => document.removeEventListener('mousedown', handleClick)
  }, [])

  const toggle = (val: string) => {
    onChange(selected.includes(val) ? selected.filter(v => v !== val) : [...selected, val])
  }

  return (
    <div ref={ref} className="relative">
      <label className="block text-sm font-medium mb-1.5" style={{ color: 'var(--text-secondary)' }}>{label}</label>
      <button
        onClick={() => setOpen(!open)}
        className="w-full flex items-center justify-b
ExpertLibraryPage function · typescript · L431-L779 (349 LOC)
app/services/expert-library/page.tsx
export default function ExpertLibraryPage() {
  const { locale, t } = useI18n()
  const router = useRouter()
  const searchParams = useSearchParams()
  const isRtl = locale === 'ar'

  // Simulated member state — always true for prototype
  const isMember = true

  // State from URL params
  const [searchQuery, setSearchQuery] = useState(searchParams.get('q') || '')
  const [debouncedSearch, setDebouncedSearch] = useState(searchQuery)
  const [selectedCategories, setSelectedCategories] = useState<string[]>(
    searchParams.get('categories')?.split(',').filter(Boolean) || []
  )
  const [selectedInstitutions, setSelectedInstitutions] = useState<string[]>(
    searchParams.get('institutions')?.split(',').filter(Boolean) || []
  )
  const [selectedLanguage, setSelectedLanguage] = useState(searchParams.get('language') || '')
  const [sortBy, setSortBy] = useState(searchParams.get('sort') || 'newest')
  const [selectedReport, setSelectedReport] = useState<Report | null>(null)
  const [load
MemberAccessGuard function · typescript · L21-L55 (35 LOC)
app/services/flagship-reports/page.tsx
function MemberAccessGuard({ locale, t }: { locale: string; t: { memberOnly: string; memberOnlyDesc: string; loginAccess: string; becomeMember: string } }) {
  const isRtl = locale === 'ar'
  return (
    <div className={`min-h-screen bg-gradient-to-b from-gray-50 via-gray-100 to-gray-200 dark:from-[#000C14] dark:via-[#001520] dark:to-[#001B30] ${isRtl ? 'rtl' : 'ltr'}`} dir={isRtl ? 'rtl' : 'ltr'}>
      <div className="flex items-center justify-center min-h-screen px-4">
        <div className="max-w-md w-full text-center">
          <div className="w-20 h-20 mx-auto mb-6 rounded-2xl bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center">
            <svg className="w-10 h-10 text-amber-600 dark:text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
            </svg>
 
SectorBadge function · typescript · L58-L63 (6 LOC)
app/services/flagship-reports/page.tsx
function SectorBadge({ sector, locale }: { sector: string; locale: string }) {
  const isRtl = locale === 'ar'
  const c = sectorColors[sector] || { bg: 'bg-gray-100 dark:bg-white/10', text: 'text-gray-800 dark:text-gray-300', border: 'border-gray-200 dark:border-white/10' }
  const label = isRtl ? (sectorsAr[sector] || sector) : sector
  return <span className={`px-2.5 py-0.5 rounded-full text-xs font-medium border ${c.bg} ${c.text} ${c.border}`}>{label}</span>
}
LanguageBadge function · typescript · L66-L74 (9 LOC)
app/services/flagship-reports/page.tsx
function LanguageBadge({ language, locale }: { language: string; locale: string }) {
  const isRtl = locale === 'ar'
  const label = isRtl ? (languagesAr[language] || language) : language
  return (
    <span className="px-2 py-0.5 rounded text-xs bg-gray-100 dark:bg-white/5 text-gray-500 dark:text-white/40 border border-gray-200 dark:border-white/10">
      {label}
    </span>
  )
}
ReportCardSkeleton function · typescript · L77-L94 (18 LOC)
app/services/flagship-reports/page.tsx
function ReportCardSkeleton() {
  return (
    <div className="rounded-2xl p-6 theme-panel animate-pulse">
      <div className="flex items-center gap-2 mb-3">
        <div className="h-5 bg-gray-200 dark:bg-white/10 rounded-full w-24" />
        <div className="h-5 bg-gray-200 dark:bg-white/10 rounded-full w-16" />
      </div>
      <div className="h-5 bg-gray-200 dark:bg-white/10 rounded w-full mb-2" />
      <div className="h-4 bg-gray-200 dark:bg-white/10 rounded w-3/4 mb-4" />
      <div className="h-4 bg-gray-200 dark:bg-white/10 rounded w-full mb-2" />
      <div className="h-4 bg-gray-200 dark:bg-white/10 rounded w-2/3 mb-4" />
      <div className="flex items-center justify-between pt-3 border-t border-gray-100 dark:border-white/5">
        <div className="h-3 bg-gray-200 dark:bg-white/10 rounded w-32" />
        <div className="h-8 bg-gray-200 dark:bg-white/10 rounded-lg w-24" />
      </div>
    </div>
  )
}
Repobility's GitHub App fixes findings like these · https://github.com/apps/repobility-bot
FlagshipReportCard function · typescript · L97-L198 (102 LOC)
app/services/flagship-reports/page.tsx
function FlagshipReportCard({ report, locale, onView, onDownload }: {
  report: Report
  locale: string
  onView: (r: Report) => void
  onDownload: (r: Report) => void
}) {
  const isRtl = locale === 'ar'
  const title = isRtl ? report.titleAr : report.title
  const summary = isRtl ? report.summaryAr : report.summary
  const edition = isRtl ? report.editionAr : report.edition
  const findings = isRtl ? report.keyFindingsAr : report.keyFindings
  const fl = isRtl
    ? { flagship: 'تقرير رئيسي', pages: 'صفحة', downloads: 'تحميل', read: 'قراءة التقرير' }
    : { flagship: 'ADCCI Flagship', pages: 'pages', downloads: 'downloads', read: 'Read Report' }

  return (
    <div
      className="rounded-2xl theme-panel hover:shadow-xl transition-all group border border-transparent hover:border-amber-300/50 dark:hover:border-amber-500/30 overflow-hidden"
    >
      <div className="flex flex-col md:flex-row">
        {/* Cover Placeholder */}
        <div className="md:w-48 lg:w-56 flex-shrink-0 
SectorialReportCard function · typescript · L201-L272 (72 LOC)
app/services/flagship-reports/page.tsx
function SectorialReportCard({ report, locale, onView, onDownload }: {
  report: SectorialReport
  locale: string
  onView: (r: Report) => void
  onDownload: (r: Report) => void
}) {
  const isRtl = locale === 'ar'
  const title = isRtl ? report.titleAr : report.title
  const summary = isRtl ? report.summaryAr : report.summary
  const edition = isRtl ? report.editionAr : report.edition
  const fl = isRtl
    ? { adcci: 'غرفة أبوظبي', findings: 'نتائج رئيسية', pages: 'صفحة', view: 'عرض التفاصيل' }
    : { adcci: 'ADCCI Authored', findings: 'Key Findings', pages: 'pages', view: 'View Details' }

  return (
    <button
      onClick={() => onView(report)}
      className="text-start rounded-2xl p-6 theme-panel hover:shadow-lg transition-all group w-full"
    >
      {/* Badges */}
      <div className="flex items-center gap-2 mb-3 flex-wrap">
        <SectorBadge sector={report.sector} locale={locale} />
        <LanguageBadge language={report.language} locale={locale} />
      </div>

  
ReportDetailModal function · typescript · L275-L454 (180 LOC)
app/services/flagship-reports/page.tsx
function ReportDetailModal({ report, locale, onClose, onViewRelated, onDownload, relatedReports }: {
  report: Report
  locale: string
  onClose: () => void
  onViewRelated: (r: Report) => void
  onDownload: (r: Report) => void
  relatedReports: Report[]
}) {
  const isRtl = locale === 'ar'
  const [copied, setCopied] = useState(false)

  useEffect(() => {
    document.body.style.overflow = 'hidden'
    const handleEsc = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose() }
    window.addEventListener('keydown', handleEsc)
    return () => { document.body.style.overflow = ''; window.removeEventListener('keydown', handleEsc) }
  }, [onClose])

  const title = isRtl ? report.titleAr : report.title
  const summary = isRtl ? report.summaryAr : report.summary
  const edition = isRtl ? report.editionAr : report.edition
  const findings = isRtl ? report.keyFindingsAr : report.keyFindings
  const tags = isRtl ? report.tagsAr : report.tags
  const isSectorial = report.type === 'Sectorial'
SectorNavigator function · typescript · L457-L510 (54 LOC)
app/services/flagship-reports/page.tsx
function SectorNavigator({ selectedSector, onSelect, locale, sectorCounts }: {
  selectedSector: string
  onSelect: (s: string) => void
  locale: string
  sectorCounts: Record<string, number>
}) {
  const isRtl = locale === 'ar'
  const fl = isRtl
    ? { title: 'استكشف حسب القطاع', all: 'جميع القطاعات', reports: 'تقارير' }
    : { title: 'Explore by Sector', all: 'All Sectors', reports: 'reports' }

  return (
    <div className="rounded-2xl theme-panel p-5 mb-8">
      <div className="flex items-center justify-between mb-4">
        <h2 className="text-base font-semibold text-gray-900 dark:text-white">{fl.title}</h2>
        {selectedSector && (
          <button
            onClick={() => onSelect('')}
            className="text-xs font-medium"
            style={{ color: 'var(--primary)' }}
          >
            {fl.all}
          </button>
        )}
      </div>
      <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-3">
        {sectors.map((sector) => {
    
FlagshipReportsPage function · typescript · L513-L909 (397 LOC)
app/services/flagship-reports/page.tsx
export default function FlagshipReportsPage() {
  const { locale, t } = useI18n()
  const router = useRouter()
  const searchParams = useSearchParams()
  const isRtl = locale === 'ar'
  const fl = t.flagshipReports
  const isMember = true

  // State
  const [searchQuery, setSearchQuery] = useState(searchParams.get('q') || '')
  const [debouncedSearch, setDebouncedSearch] = useState(searchQuery)
  const [selectedSector, setSelectedSector] = useState(searchParams.get('sector') || '')
  const [selectedLanguage, setSelectedLanguage] = useState(searchParams.get('language') || '')
  const [selectedYear, setSelectedYear] = useState(searchParams.get('year') || '')
  const [sortBy, setSortBy] = useState(searchParams.get('sort') || 'newest')
  const [selectedReport, setSelectedReport] = useState<Report | null>(null)
  const [loading, setLoading] = useState(true)

  const sectorialSectionRef = useRef<HTMLDivElement>(null)

  // Loading simulation
  useEffect(() => {
    const timer = setTimeout(
daysUntil function · typescript · L21-L27 (7 LOC)
app/services/global-tenders-hub/page.tsx
function daysUntil(deadline: string): number {
  const now = new Date()
  now.setHours(0, 0, 0, 0)
  const dl = new Date(deadline)
  dl.setHours(0, 0, 0, 0)
  return Math.ceil((dl.getTime() - now.getTime()) / (1000 * 60 * 60 * 24))
}
formatCountdown function · typescript · L29-L34 (6 LOC)
app/services/global-tenders-hub/page.tsx
function formatCountdown(days: number, isRtl: boolean): string {
  if (days < 0) return isRtl ? 'انتهى' : 'Expired'
  if (days === 0) return isRtl ? 'اليوم' : 'Today'
  if (days === 1) return isRtl ? 'غداً' : 'Tomorrow'
  return isRtl ? `${days} يوم` : `${days} days`
}
MemberAccessGuard function · typescript · L37-L77 (41 LOC)
app/services/global-tenders-hub/page.tsx
function MemberAccessGuard({ locale }: { locale: string }) {
  const isRtl = locale === 'ar'
  return (
    <div className={`min-h-screen bg-gradient-to-b from-gray-50 via-gray-100 to-gray-200 dark:from-[#000C14] dark:via-[#001520] dark:to-[#001B30] ${isRtl ? 'rtl' : 'ltr'}`} dir={isRtl ? 'rtl' : 'ltr'}>
      <div className="flex items-center justify-center min-h-screen px-4">
        <div className="max-w-md w-full text-center">
          <div className="w-20 h-20 mx-auto mb-6 rounded-2xl bg-amber-100 dark:bg-amber-900/30 flex items-center justify-center">
            <svg className="w-10 h-10 text-amber-600 dark:text-amber-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
              <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
            </svg>
          </div>
          <h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-3">
Want this analysis on your repo? https://repobility.com/scan/
OrgAvatar function · typescript · L80-L89 (10 LOC)
app/services/global-tenders-hub/page.tsx
function OrgAvatar({ name, size = 'md' }: { name: string; size?: 'sm' | 'md' }) {
  const colors = organizationColors[name] || { bg: 'bg-gray-500', text: 'text-white' }
  const initials = name.split(/[\s&]+/).map(w => w[0]).join('').slice(0, 2).toUpperCase()
  const sizeClasses = size === 'sm' ? 'w-8 h-8 text-xs' : 'w-10 h-10 text-sm'
  return (
    <div className={`${sizeClasses} ${colors.bg} ${colors.text} rounded-lg flex items-center justify-center font-bold flex-shrink-0`}>
      {initials}
    </div>
  )
}
StatusBadge function · typescript · L98-L105 (8 LOC)
app/services/global-tenders-hub/page.tsx
function StatusBadge({ status }: { status: string }) {
  const color = statusColorMap[status] || statusColorMap.Open
  return (
    <span className={`px-2.5 py-0.5 rounded-full text-xs font-medium border ${color}`}>
      {status}
    </span>
  )
}
SectorBadge function · typescript · L119-L126 (8 LOC)
app/services/global-tenders-hub/page.tsx
function SectorBadge({ sector }: { sector: string }) {
  const color = sectorColorMap[sector] || 'bg-gray-100 dark:bg-white/10 text-gray-800 dark:text-gray-300 border-gray-200 dark:border-white/10'
  return (
    <span className={`px-2.5 py-0.5 rounded-full text-xs font-medium border ${color}`}>
      {sector}
    </span>
  )
}
‹ prevpage 2 / 4next ›