/opt/canhelp/apps/web/src/app/specialists
Edit: /opt/canhelp/apps/web/src/app/specialists/page.tsx (10839B)
import Link from 'next/link'
import { cookies } from 'next/headers'
import { getSpecialists, getCategories, getLocations } from '@/lib/api'
import { getTranslations } from '@/lib/translations'
import { formatShortName } from '@/lib/formatName'
import { formatLastSeen } from '@/lib/formatLastSeen'
import { FavoriteSpecialistButton } from '@/components/FavoriteSpecialistButton'
import { SpecialistsFiltersClient } from '@/components/SpecialistsFiltersClient'
import { PlanBadge } from '@/components/PlanBadge'
import type { Locale } from '@/lib/translations'
interface Props {
searchParams: Promise
>
}
export default async function SpecialistsPage({ searchParams }: Props) {
const params = await searchParams
const page = Number(params.page || 1)
const cookieStore = await cookies()
const locale = (cookieStore.get('canhelp_locale')?.value ?? 'el') as Locale
const t = getTranslations(locale)
const cookieLocation = decodeURIComponent(cookieStore.get('canhelp_location')?.value ?? '')
// Location filter only applies when explicitly set in URL params, not from cookie
const effectiveLocation = params.location
let specialists: any[] = []
let total = 0
let catTree: any[] = []
let locationTree: any[] = []
let favoriteIds = new Set()
const BASE = process.env.INTERNAL_API_URL || 'http://localhost:4000'
const cookieHeader = cookieStore.getAll().map((c: any) => `${c.name}=${c.value}`).join('; ')
try {
const [result, cats, locs] = await Promise.all([
getSpecialists({ page: String(page), limit: '20', locale, ...params, ...(effectiveLocation ? { location: effectiveLocation } : {}) }),
getCategories().catch(() => [] as any[]),
getLocations().catch(() => [] as any[]),
])
console.log('[SpecialistsPage] got', result.total, 'specialists, data.length=', result.data.length)
specialists = result.data
total = result.total
catTree = cats as any[]
locationTree = locs as any[]
} catch (err) {
console.error('[SpecialistsPage] fetch error:', err)
}
if (cookieHeader) {
try {
const favRes = await fetch(`${BASE}/api/favorites/specialists`, {
headers: { Cookie: cookieHeader },
})
if (favRes.ok) {
const favData = await favRes.json()
favoriteIds = new Set(favData.map((row: any) => row.specialist?.id).filter(Boolean))
}
} catch {}
}
const totalPages = Math.ceil(total / 20)
const dateLocale = locale === 'ru' ? 'ru-RU' : locale === 'uk' ? 'uk-UA' : locale === 'en' ? 'en-US' : 'el-GR'
const catLocale = locale === 'en' ? 'en' : locale === 'ru' ? 'ru' : locale === 'uk' ? 'uk' : 'el'
const buildUrl = (overrides: Record) => {
const merged = { ...params, ...(effectiveLocation ? { location: effectiveLocation } : {}), ...overrides }
const qs = Object.entries(merged)
.filter(([, v]) => v !== undefined && v !== '')
.map(([k, v]) => `${k}=${encodeURIComponent(v!)}`)
.join('&')
return `/specialists${qs ? '?' + qs : ''}`
}
return (
{/* Sidebar search */}
{/* Specialists list */}
{(params.q || params.category || params.location || params.language) && (
{total} {t('specialists.title').toLowerCase()}
)}
{specialists.length === 0 ? (
) : (
{specialists.map((user: any) => {
const displayName = formatShortName(user.firstName, user.lastName, user.name)
const initials =
`${user.firstName?.[0] ?? ''}${user.lastName?.[0] ?? ''}`.toUpperCase() ||
user.name?.[0]?.toUpperCase() ||
'?'
return (
{/* Stretched link — covers the whole card for navigation */}
{/* Avatar */}
{user.image ? (

) : (
initials
)}
{displayName}
{user.planTier && user.planTier !== 'free' && (
)}
{user.hasVerifiedBadge && user.planTier === 'free' && (
)}
{Array.isArray(user.activeStatuses) && user.activeStatuses.includes('can_help_now') && (
{t('status.can_help_now')}
)}
{user.rating && (
★
{Number(user.rating).toFixed(1)}
)}
{user.todayStatus === 'available' && (
{t('schedule.available')}
)}
{user.todayStatus === 'busy' && (
{t('schedule.busy')}
)}
{/* Card titles */}
{user.cards && user.cards.length > 0 && (
{user.cards.slice(0, 2).map((card: any) => (
{card.title}
))}
{user.cards.length > 2 && (
+{user.cards.length - 2}
)}
)}
{/* Bio */}
{user.bio || t('specialists.no_bio')}
{/* Last seen */}
{user.lastSeenAt && (
{formatLastSeen(user.lastSeenAt, t)}
)}
)
})}
)}
{/* Pagination */}
{totalPages > 1 && (
{Array.from({ length: totalPages }, (_, i) => i + 1).map((p) => (
{p}
))}
)}
)
}