Function bodies 182 total
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:', erfetchApplication 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')
linkloadData 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={2DatasetCardSkeleton 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 grDatasetDetailModal 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.charMultiSelect 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-bDataHubPage 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<strMemberAccessGuard 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-900ReportDetailModal 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-bExpertLibraryPage 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 [loadMemberAccessGuard 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>
)
}