diff --git a/frontend/src/components/SortBy.tsx b/frontend/src/components/SortBy.tsx
new file mode 100644
index 000000000..19974db81
--- /dev/null
+++ b/frontend/src/components/SortBy.tsx
@@ -0,0 +1,57 @@
+import { faCaretDown, faCaretUp, faCheck } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import { useState } from 'react'
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuTrigger,
+} from 'components/ui/dropdownMenu'
+
+interface SortOption {
+ value: string
+ label: string
+}
+
+interface SortByProps {
+ options: SortOption[]
+ selectedOption: string
+ onSortChange: (value: string) => void
+}
+
+const SortBy = ({ options, selectedOption, onSortChange }: SortByProps) => {
+ const [open, setOpen] = useState
(false)
+ const handleOpenChange = (isOpen: boolean) => {
+ setOpen(isOpen)
+ }
+
+ if (!options || options.length === 0) return null
+
+ return (
+
+
+
+
+
+ {options.map((option) => (
+ onSortChange(option.value)}
+ className="justify-between"
+ >
+ {option.label}
+ {option.value === selectedOption && }
+
+ ))}
+
+
+ )
+}
+
+export default SortBy
diff --git a/frontend/src/components/SponsorButton.tsx b/frontend/src/components/SponsorButton.tsx
deleted file mode 100644
index 7075d8432..000000000
--- a/frontend/src/components/SponsorButton.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-// import { faHeart } from '@fortawesome/free-regular-svg-icons'
-import { faHeart as faRegularHeart } from '@fortawesome/free-regular-svg-icons' // Outline Heart
-import { faHeart as faSolidHeart } from '@fortawesome/free-solid-svg-icons'
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
-import { useState } from 'react'
-
-const SponsorButton = () => {
- const [isHovered, setIsHovered] = useState(false)
- return (
- setIsHovered(true)}
- onMouseLeave={() => setIsHovered(false)}
- >
-
- Sponsor
-
- )
-}
-
-export default SponsorButton
diff --git a/frontend/src/components/ui/dropdownMenu.tsx b/frontend/src/components/ui/dropdownMenu.tsx
new file mode 100644
index 000000000..a52b7955a
--- /dev/null
+++ b/frontend/src/components/ui/dropdownMenu.tsx
@@ -0,0 +1,184 @@
+import { faCheck, faChevronRight, faDotCircle } from '@fortawesome/free-solid-svg-icons'
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
+import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
+import * as React from 'react'
+import { cn } from 'utils/utility'
+
+const DropdownMenu = DropdownMenuPrimitive.Root
+
+const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
+
+const DropdownMenuGroup = DropdownMenuPrimitive.Group
+
+const DropdownMenuPortal = DropdownMenuPrimitive.Portal
+
+const DropdownMenuSub = DropdownMenuPrimitive.Sub
+
+const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
+
+const DropdownMenuSubTrigger = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, children, ...props }, ref) => (
+
+ {children}
+
+
+))
+DropdownMenuSubTrigger.displayName = DropdownMenuPrimitive.SubTrigger.displayName
+
+const DropdownMenuSubContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSubContent.displayName = DropdownMenuPrimitive.SubContent.displayName
+
+const DropdownMenuContent = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, sideOffset = 4, ...props }, ref) => (
+
+
+
+))
+DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
+
+const DropdownMenuItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+ svg]:size-4 [&>svg]:shrink-0',
+ inset && 'pl-8',
+ className
+ )}
+ {...props}
+ />
+))
+DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
+
+const DropdownMenuCheckboxItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, checked, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuCheckboxItem.displayName = DropdownMenuPrimitive.CheckboxItem.displayName
+
+const DropdownMenuRadioItem = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+
+
+
+
+ {children}
+
+))
+DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
+
+const DropdownMenuLabel = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef & {
+ inset?: boolean
+ }
+>(({ className, inset, ...props }, ref) => (
+
+))
+DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
+
+const DropdownMenuSeparator = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+))
+DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
+
+const DropdownMenuShortcut = ({ className, ...props }: React.HTMLAttributes) => {
+ return
+}
+DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
+
+export {
+ DropdownMenu,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuGroup,
+ DropdownMenuPortal,
+ DropdownMenuSub,
+ DropdownMenuSubContent,
+ DropdownMenuSubTrigger,
+ DropdownMenuRadioGroup,
+}
diff --git a/frontend/src/hooks/useSearchPage.ts b/frontend/src/hooks/useSearchPage.ts
index 879b30535..0348f292c 100644
--- a/frontend/src/hooks/useSearchPage.ts
+++ b/frontend/src/hooks/useSearchPage.ts
@@ -7,6 +7,7 @@ import { handleAppError } from 'wrappers/ErrorWrapper'
interface UseSearchPageOptions {
indexName: string
pageTitle: string
+ defaultSortBy?: string
}
interface UseSearchPageReturn {
@@ -15,21 +16,23 @@ interface UseSearchPageReturn {
currentPage: number
totalPages: number
searchQuery: string
-
+ sortBy: string
handleSearch: (query: string) => void
-
handlePageChange: (page: number) => void
+ handleSortChange: (sort: string) => void
}
export function useSearchPage({
indexName,
pageTitle,
+ defaultSortBy = '',
}: UseSearchPageOptions): UseSearchPageReturn {
const navigate = useNavigate()
const [searchParams, setSearchParams] = useSearchParams()
const [items, setItems] = useState([])
const [currentPage, setCurrentPage] = useState(parseInt(searchParams.get('page') || '1'))
const [searchQuery, setSearchQuery] = useState(searchParams.get('q') || '')
+ const [sortBy, setSortBy] = useState(searchParams.get('sortBy') || defaultSortBy)
const [totalPages, setTotalPages] = useState(0)
const [isLoaded, setIsLoaded] = useState(false)
@@ -37,8 +40,9 @@ export function useSearchPage({
const params = new URLSearchParams()
if (searchQuery) params.set('q', searchQuery)
if (currentPage > 1) params.set('page', currentPage.toString())
+ if (sortBy && sortBy !== 'projects') params.set('sortBy', sortBy)
setSearchParams(params)
- }, [searchQuery, currentPage, setSearchParams])
+ }, [searchQuery, currentPage, sortBy, setSearchParams])
useEffect(() => {
document.title = pageTitle
@@ -47,7 +51,7 @@ export function useSearchPage({
const fetchData = async () => {
try {
const data: AlgoliaResponseType = await fetchAlgoliaData(
- indexName,
+ sortBy ? `${indexName}_${sortBy}` : indexName,
searchQuery,
currentPage
)
@@ -60,7 +64,7 @@ export function useSearchPage({
}
fetchData()
- }, [currentPage, searchQuery, indexName, pageTitle, navigate])
+ }, [currentPage, searchQuery, sortBy, indexName, pageTitle, navigate])
const handleSearch = (query: string) => {
setSearchQuery(query)
@@ -75,13 +79,20 @@ export function useSearchPage({
})
}
+ const handleSortChange = (sort: string) => {
+ setSortBy(sort)
+ setCurrentPage(1)
+ }
+
return {
items,
isLoaded,
currentPage,
totalPages,
searchQuery,
+ sortBy,
handleSearch,
handlePageChange,
+ handleSortChange,
}
}
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 12b369f78..3c8a70c86 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,3 +1,4 @@
+import { ChakraProvider, defaultSystem } from '@chakra-ui/react'
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
@@ -20,7 +21,9 @@ createRoot(document.getElementById('root')!).render(
-
+
+
+
diff --git a/frontend/src/pages/ChapterDetails.tsx b/frontend/src/pages/ChapterDetails.tsx
index ea8aa8794..555aeb54e 100644
--- a/frontend/src/pages/ChapterDetails.tsx
+++ b/frontend/src/pages/ChapterDetails.tsx
@@ -33,7 +33,7 @@ const ChapterDetailsPage = () => {
)
- if (!chapter)
+ if (!chapter || !chapter.is_active)
return (