/opt/canhelp/apps/web/src/app/tasks
NameSizeModeActions
new/-0755rm
[id]/-0755rm
page.tsx101240644editdlrm
TaskFiltersClient.tsx76440644editdlrm
TaskFiltersForm.tsx4930644editdlrm
TaskListClient.tsx57510644editdlrm
TaskMapClient.tsx92400644editdlrm
TaskMapDynamic.tsx6580644editdlrm
TaskModeToggle.tsx15890644editdlrm
TasksAuthGuard.tsx650644editdlrm
TaskViewToggle.tsx21320644editdlrm
Edit: /opt/canhelp/apps/web/src/app/tasks/page.tsx (10124B)
import Link from 'next/link' import { cookies } from 'next/headers' import { getCategories, getLocations, getTaskBudgetRange } from '@/lib/api' import { getTranslations } from '@/lib/translations' import type { Locale } from '@/lib/translations' import { TaskListClient } from './TaskListClient' import { TaskFiltersClient } from './TaskFiltersClient' import { TasksAuthGuard } from './TasksAuthGuard' import { TaskMapDynamic } from './TaskMapDynamic' import { TaskViewToggle } from './TaskViewToggle' // SSR uses INTERNAL_API_URL (or localhost:4000 directly) to avoid external DNS/CORS round-trip const API_BASE = typeof window === 'undefined' ? (process.env.INTERNAL_API_URL || 'http://localhost:4000') : (process.env.NEXT_PUBLIC_API_URL || 'http://localhost:4000') interface Props { searchParams: Promise> } export default async function TasksPage({ 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 cookieLocation = decodeURIComponent(cookieStore.get('canhelp_location')?.value ?? '') const t = getTranslations(locale) const catLocale = locale === 'en' ? 'en' : locale === 'ru' ? 'ru' : locale === 'uk' ? 'uk' : 'el' const cookieHeader = cookieStore.getAll().map(c => `${c.name}=${c.value}`).join('; ') // Detect if user is a logged-in specialist (has session cookie) let isSpecialistSession = false if (cookieHeader) { try { const INTERNAL = process.env.INTERNAL_API_URL || 'http://localhost:4000' const sessRes = await fetch(`${INTERNAL}/api/auth/get-session`, { headers: { Cookie: cookieHeader } }) if (sessRes.ok) { const sess = await sessRes.json() const role = sess?.user?.role isSpecialistSession = role === 'specialist' || role === 'admin' } } catch {} } // Guests and customers default to 'all'; specialists default to 'for_me' const mode: 'for_me' | 'all' = params.mode === 'all' ? 'all' : params.mode === 'for_me' ? 'for_me' : isSpecialistSession ? 'for_me' : 'all' let tasks: any[] = [] let total = 0 let noCards = false let categories: any[] = [] let locationTree: any[] = [] let budgetRange = { min: 0, max: 0 } // In 'all' mode only respect explicit URL param; cookie location only applies to 'for_me' // (for_me filtering is handled server-side by specialist cards, so cookie is unused there too) const effectiveLocation = params.location ?? (mode === 'for_me' ? cookieLocation : '') const { q, category } = params const rangeParams: Record = {} if (q) rangeParams.q = q if (category) rangeParams.category = category if (effectiveLocation) rangeParams.location = effectiveLocation const effectiveParams: Record = { page: String(page), limit: '10', ...params, ...(effectiveLocation ? { location: effectiveLocation } : {}), } delete effectiveParams.mode try { ;[categories, locationTree, budgetRange] = await Promise.all([ getCategories(), getLocations(), getTaskBudgetRange(Object.keys(rangeParams).length ? rangeParams : undefined), ]) } catch {} try { if (mode === 'for_me') { const forMeParams = new URLSearchParams(effectiveParams) const res = await fetch(`${API_BASE}/api/tasks/for-me?${forMeParams}`, { headers: { Cookie: cookieHeader }, cache: 'no-store', }) if (res.ok) { const json = await res.json() if (json.noCards) { noCards = true } else { tasks = json.data ?? [] total = json.total ?? 0 } } } else { const allParams = new URLSearchParams(effectiveParams) const res = await fetch(`${API_BASE}/api/tasks?${allParams}`, { headers: { Cookie: cookieHeader }, cache: 'no-store', }) if (res.ok) { const json = await res.json() tasks = json.data ?? [] total = json.total ?? 0 } } } catch {} const catMap: Record = {} for (const cat of categories) { catMap[cat.slug] = (cat.names as any)[catLocale] ?? cat.names.el for (const sub of cat.children ?? []) { catMap[sub.slug] = (sub.names as any)[catLocale] ?? sub.names.el } } const locMap: Record = {} for (const city of locationTree) { locMap[city.slug] = (city.names as any)[catLocale] ?? city.names.el for (const d of city.children ?? []) { locMap[d.slug] = (d.names as any)[catLocale] ?? d.names.el } } const view = params.view === 'map' ? 'map' : 'list' const hasFilter = !!(params.q || params.category || params.location || params.budgetMin || params.budgetMax) const filterParams: Record = {} if (params.q) filterParams.q = params.q if (params.category) filterParams.category = params.category if (params.location) filterParams.location = params.location if (params.budgetMin) filterParams.budgetMin = params.budgetMin if (params.budgetMax) filterParams.budgetMax = params.budgetMax return (
{view === 'map' ? ( /* ── Full-screen map layout ────────────────────────────── */
{/* Floating filters panel — left side */}
{t('tasks.filters')}
{/* View toggle floats top-right over the map */}
) : ( /* ── List layout ───────────────────────────────────────── */
{/* Sidebar filters */} {/* Task list */}

{hasFilter ? <>{total} {t('tasks.title')} : t('nav.tasks')}

{noCards ? (
🗂️

{t('find_tasks.no_cards')}

{t('find_tasks.add_card')}
) : ( )}
)}
) }