Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 71 additions & 11 deletions app/organizations/organizations-client.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client'

import { useState, useEffect, useCallback, useMemo, useRef, startTransition } from 'react'
import { useState, useEffect, useCallback, useMemo, useRef, startTransition, useId } from 'react'
import { useRouter, useSearchParams } from 'next/navigation'
import { Search, X } from 'lucide-react'
import { Search, X, Filter } from 'lucide-react'
import { Button, Input, SectionHeader } from '@/components/ui'
import { Organization, PaginatedResponse } from '@/lib/api'
import { OrganizationCard } from '@/components/organization-card'
Expand All @@ -22,9 +22,11 @@ interface OrganizationsClientProps {
export function OrganizationsClient({ initialData, initialPage, initialTechs, firstTimeCount }: OrganizationsClientProps) {
const router = useRouter()
const searchParams = useSearchParams()
const filtersTitleId = useId()
const [data, setData] = useState<PaginatedResponse<Organization>>(initialData)
const [isLoading, setIsLoading] = useState(false)
const [currentPage, setCurrentPage] = useState(initialPage)
const [isMobileFiltersOpen, setIsMobileFiltersOpen] = useState(false)
const isInitialMount = useRef(true)
const lastFetchParams = useRef<string>('')
const lastUrlString = useRef<string>('')
Expand Down Expand Up @@ -338,15 +340,27 @@ export function OrganizationsClient({ initialData, initialPage, initialTechs, fi
className="max-w-3xl mx-auto mb-8"
/>
{/* Search Bar */}
<div className="relative max-w-xl mx-auto mb-5">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="Search organizations by name, technology, or keyword..."
className="pl-10 h-12 text-base"
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
/>
<div className="max-w-xl mx-auto mb-5 space-y-3">
<div className="relative">
<Search className="absolute left-4 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
type="search"
placeholder="Search organizations by name, technology, or keyword..."
className="pl-10 h-12 text-base"
value={searchInput}
onChange={(e) => setSearchInput(e.target.value)}
/>
</div>
<div className="flex justify-center lg:hidden">
<Button
variant="outline"
className="h-10 px-5 rounded-full transition-colors bg-background"
onClick={() => setIsMobileFiltersOpen(true)}
>
<Filter className="h-4 w-4 mr-2" />
Filters
</Button>
</div>
</div>

{/* Filter Chips Row */}
Expand Down Expand Up @@ -556,6 +570,52 @@ export function OrganizationsClient({ initialData, initialPage, initialTechs, fi
)}
</div>
</div>

{/* Mobile Filters Overlay */}
{isMobileFiltersOpen && (
<div className="fixed inset-0 z-50 flex lg:hidden">
{/* Overlay backdrop */}
<div
className="fixed inset-0 bg-background/80 backdrop-blur-sm"
onClick={() => setIsMobileFiltersOpen(false)}
aria-hidden="true"
/>
{/* Sidebar Drawer */}
<div
className="relative flex flex-col w-full max-w-xs bg-background h-full shadow-xl"
role="dialog"
aria-modal="true"
aria-labelledby={filtersTitleId}
>
<div className="flex items-center justify-between px-4 py-3 border-b">
<h2 id={filtersTitleId} className="text-lg font-semibold">Filters</h2>
<Button
variant="ghost"
size="icon"
aria-label="Close filters"
onClick={() => setIsMobileFiltersOpen(false)}
>
<X className="h-5 w-5" />
</Button>
</div>
<div className="flex-1 overflow-y-auto w-full custom-scrollbar">
<div className="p-4">
<FiltersSidebar
onFilterChange={handleFilterChange}
filters={filters}
availableTechs={initialTechs}
firstTimeCount={firstTimeCount}
/>
</div>
</div>
<div className="p-4 border-t bg-background">
<Button className="w-full" onClick={() => setIsMobileFiltersOpen(false)}>
Show Results
</Button>
</div>
</div>
</div>
)}
</div>
)
}
Expand Down