diff --git a/src/features/feedback/components/DashboardFilterBar/DashboardFilterBar.tsx b/src/features/feedback/components/DashboardFilterBar/DashboardFilterBar.tsx
new file mode 100644
index 00000000..f5e8a0cd
--- /dev/null
+++ b/src/features/feedback/components/DashboardFilterBar/DashboardFilterBar.tsx
@@ -0,0 +1,31 @@
+import { Flex, Spacer } from '@chakra-ui/react'
+import { useFilterFeedback } from '../../api/useFilterFeedback'
+import { FilterSearchbar } from './FilterSearchbar'
+import { FilterSelect } from './FilterSelect'
+
+export const DashboardFilterBar = (): JSX.Element => {
+ const { filter, order, handleFilterChange, handleOrderChange, isLoading } =
+ useFilterFeedback()
+
+ return (
+
+
+
+
+
+
+ )
+}
diff --git a/src/features/feedback/components/DashboardFilterBar/FilterSearchbar.tsx b/src/features/feedback/components/DashboardFilterBar/FilterSearchbar.tsx
new file mode 100644
index 00000000..14dba6c2
--- /dev/null
+++ b/src/features/feedback/components/DashboardFilterBar/FilterSearchbar.tsx
@@ -0,0 +1,24 @@
+import { Box } from '@chakra-ui/react'
+import { Searchbar } from '@opengovsg/design-system-react'
+import { useRouter } from 'next/router'
+import { useCallback } from 'react'
+
+export const FilterSearchbar = (): JSX.Element => {
+ const router = useRouter()
+ const handleSearch = useCallback(
+ (value: string) => {
+ router.push(`/search?q=${value}`)
+ },
+ [router]
+ )
+
+ return (
+
+
+
+ )
+}
diff --git a/src/features/feedback/components/TeamFeedbackFilterBar.tsx b/src/features/feedback/components/DashboardFilterBar/FilterSelect.tsx
similarity index 62%
rename from src/features/feedback/components/TeamFeedbackFilterBar.tsx
rename to src/features/feedback/components/DashboardFilterBar/FilterSelect.tsx
index a6622393..eb62bdfd 100644
--- a/src/features/feedback/components/TeamFeedbackFilterBar.tsx
+++ b/src/features/feedback/components/DashboardFilterBar/FilterSelect.tsx
@@ -1,12 +1,10 @@
import {
- Box,
- MenuItemOption,
+ Menu,
MenuList,
MenuOptionGroup,
- Menu,
+ MenuItemOption,
} from '@chakra-ui/react'
import { ChevronMenuButton } from '~/components/ChevronMenuButton'
-import { useFilterFeedback } from '../api/useFilterFeedback'
interface FilterSelectProps {
selection: {
@@ -17,7 +15,7 @@ interface FilterSelectProps {
onChange: (value: string | string[]) => void
isDisabled?: boolean
}
-const FilterSelect = ({
+export const FilterSelect = ({
selection,
onChange,
isDisabled,
@@ -47,28 +45,3 @@ const FilterSelect = ({
)
}
-
-export const TeamFeedbackFilterBar = (): JSX.Element => {
- const { filter, order, handleFilterChange, handleOrderChange, isLoading } =
- useFilterFeedback()
-
- return (
-
-
-
-
- )
-}
diff --git a/src/features/feedback/components/DashboardFilterBar/index.ts b/src/features/feedback/components/DashboardFilterBar/index.ts
new file mode 100644
index 00000000..dd4a6dba
--- /dev/null
+++ b/src/features/feedback/components/DashboardFilterBar/index.ts
@@ -0,0 +1 @@
+export { DashboardFilterBar } from './DashboardFilterBar'
diff --git a/src/features/feedback/components/index.ts b/src/features/feedback/components/index.ts
index 035432f3..0eeb484d 100644
--- a/src/features/feedback/components/index.ts
+++ b/src/features/feedback/components/index.ts
@@ -2,3 +2,4 @@ export * from './FeedbackNavbar'
export * from './TeamFeedbackList'
export * from './FeedbackComment'
export * from './FeedbackActionsModal'
+export * from './DashboardFilterBar'
diff --git a/src/pages/dashboard.tsx b/src/pages/dashboard.tsx
index 6bd01f0b..1a272aec 100644
--- a/src/pages/dashboard.tsx
+++ b/src/pages/dashboard.tsx
@@ -7,11 +7,11 @@ import feedbackUncleSvg from '~/features/feedback/assets/feedback-uncle.svg'
import {
FeedbackActionsModal,
TeamFeedbackList,
+ DashboardFilterBar,
} from '~/features/feedback/components'
import type { NextPageWithLayout } from '~/lib/types'
import { AdminLayout } from '~/templates/layouts/AdminLayout'
import { trpc } from '~/utils/trpc'
-import { TeamFeedbackFilterBar } from '~/features/feedback/components/TeamFeedbackFilterBar'
const Dashboard: NextPageWithLayout = () => {
const { data: counts, isLoading: unreadCountIsLoading } =
@@ -55,7 +55,7 @@ const Dashboard: NextPageWithLayout = () => {
borderWidth="1px"
borderColor="base.divider.medium"
>
-
+
diff --git a/src/pages/search.tsx b/src/pages/search.tsx
new file mode 100644
index 00000000..a4ac159c
--- /dev/null
+++ b/src/pages/search.tsx
@@ -0,0 +1,63 @@
+import { Box, Text } from '@chakra-ui/react'
+import { Searchbar } from '@opengovsg/design-system-react'
+import type { GetServerSideProps, InferGetServerSidePropsType } from 'next'
+import { useRouter } from 'next/router'
+import { useCallback, useState } from 'react'
+import { FeedbackComment } from '~/features/feedback/components'
+import type { NextPageWithLayout } from '~/lib/types'
+import { AdminLayout } from '~/templates/layouts/AdminLayout'
+import { trpc } from '~/utils/trpc'
+
+const Search: NextPageWithLayout<
+ InferGetServerSidePropsType
+> = ({ query }) => {
+ const router = useRouter()
+ const [queryInput, setQueryInput] = useState(query)
+ const { data, isLoading } = trpc.post.search.useQuery({ query })
+
+ const updateSearchQuery = useCallback(
+ (newQuery: string) => {
+ if (!newQuery) return
+ router.query.q = newQuery
+ router.push(router)
+ },
+ [router]
+ )
+
+ return (
+
+
+ setQueryInput(e.target.value)}
+ onSearch={updateSearchQuery}
+ />
+
+ {isLoading ? 'Searching...' : `${data?.length} results`}
+ {data?.map((d) => (
+
+ ))}
+
+ )
+}
+
+Search.getLayout = AdminLayout
+
+export const getServerSideProps: GetServerSideProps<{
+ query: string
+}> = async ({ query }) => {
+ if (!query.q) {
+ return {
+ redirect: {
+ permanent: false,
+ destination: '/',
+ },
+ }
+ }
+ return {
+ props: { query: String(query.q) },
+ }
+}
+
+export default Search
diff --git a/src/server/modules/post/post.router.ts b/src/server/modules/post/post.router.ts
index 19ff1dd7..8c735275 100644
--- a/src/server/modules/post/post.router.ts
+++ b/src/server/modules/post/post.router.ts
@@ -186,6 +186,26 @@ export const postRouter = router({
})
return updatedPost
}),
+ search: protectedProcedure
+ .input(
+ z.object({
+ query: z.string(),
+ limit: z.number().min(1).max(30).optional().default(10),
+ })
+ )
+ .query(async ({ input: { query, limit }, ctx }) => {
+ return await ctx.prisma.post.findMany({
+ take: limit,
+ where: {
+ deletedAt: null,
+ OR: [
+ { title: { contains: query, mode: 'insensitive' } },
+ { content: { contains: query, mode: 'insensitive' } },
+ ],
+ },
+ select: defaultPostSelect,
+ })
+ }),
delete: protectedProcedure
.input(z.object({ id: z.string() }))
.mutation(async ({ input: { id }, ctx }) => {