diff --git a/package-lock.json b/package-lock.json
index 51afa783..af046123 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -52,6 +52,7 @@
"pdfjs-dist": "^4.9.155",
"prettier": "^2.8.8",
"qs": "^6.12.1",
+ "query-string": "^9.1.1",
"react": "^18",
"react-canvas-confetti": "^2.0.7",
"react-day-picker": "^8.10.1",
@@ -13185,6 +13186,14 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/decode-uri-component": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.4.1.tgz",
+ "integrity": "sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==",
+ "engines": {
+ "node": ">=14.16"
+ }
+ },
"node_modules/dedent": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz",
@@ -21254,6 +21263,33 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/query-string": {
+ "version": "9.1.1",
+ "resolved": "https://registry.npmjs.org/query-string/-/query-string-9.1.1.tgz",
+ "integrity": "sha512-MWkCOVIcJP9QSKU52Ngow6bsAWAPlPK2MludXvcrS2bGZSl+T1qX9MZvRIkqUIkGLJquMJHWfsT6eRqUpp4aWg==",
+ "dependencies": {
+ "decode-uri-component": "^0.4.1",
+ "filter-obj": "^5.1.0",
+ "split-on-first": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/query-string/node_modules/filter-obj": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-5.1.0.tgz",
+ "integrity": "sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==",
+ "engines": {
+ "node": ">=14.16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/querystring-es3": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz",
@@ -23464,6 +23500,17 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/split-on-first": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-3.0.0.tgz",
+ "integrity": "sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
diff --git a/package.json b/package.json
index 47d460f1..4a44860a 100644
--- a/package.json
+++ b/package.json
@@ -64,6 +64,7 @@
"pdfjs-dist": "^4.9.155",
"prettier": "^2.8.8",
"qs": "^6.12.1",
+ "query-string": "^9.1.1",
"react": "^18",
"react-canvas-confetti": "^2.0.7",
"react-day-picker": "^8.10.1",
diff --git a/src/features/collection/components/exploration.tsx b/src/features/collection/components/exploration.tsx
index 1dd4107d..3b1846b5 100644
--- a/src/features/collection/components/exploration.tsx
+++ b/src/features/collection/components/exploration.tsx
@@ -3,17 +3,29 @@
import Icon from '@/shared/components/custom/icon'
import Collection from './collection'
import CollectionList from './collection-list'
-import Text from '@/shared/components/ui/text'
import { useCollections } from '@/requests/collection/hooks'
import Loading from '@/shared/components/custom/loading'
import { useUser } from '@/shared/hooks/use-user'
import Link from 'next/link'
import { useScrollPosition } from '@/shared/hooks/use-scroll-position'
-
-const controlButtons = ['분야', '퀴즈 유형', '문제 수']
+import SelectMinQuizCountDrawer from './select-min-quiz-count-drawer'
+import { useSearchParams } from 'next/navigation'
+import { DEFAULT_COLLECTION_QUIZ_COUNT } from '../config'
+import SelectQuizTypeDrawer from './select-quiz-type-drawer'
+import SelectCategoryDrawer from './select-category-drawer'
const Exploration = () => {
- const { data: collectionsData, isLoading } = useCollections()
+ const searchParams = useSearchParams()
+ const categories = searchParams.getAll('collection-category') as Collection.Field[]
+ const quizType = searchParams.get('quiz-type') as Quiz.Type
+ const minQuizCount = Number(searchParams.get('min-quiz-count')) || DEFAULT_COLLECTION_QUIZ_COUNT
+
+ const { data: collectionsData, isLoading } = useCollections({
+ collectionSortOption: 'POPULARITY',
+ collectionCategories: categories,
+ quizType,
+ quizCount: minQuizCount,
+ })
const { user } = useUser()
const scrollContainerRef = useScrollPosition({ pageKey: 'exploration' })
@@ -22,17 +34,9 @@ const Exploration = () => {
<>
- {controlButtons.map((button) => (
-
- ))}
+
+
+
diff --git a/src/features/collection/components/select-category-drawer.tsx b/src/features/collection/components/select-category-drawer.tsx
new file mode 100644
index 00000000..4312b5af
--- /dev/null
+++ b/src/features/collection/components/select-category-drawer.tsx
@@ -0,0 +1,117 @@
+'use client'
+
+import QS from 'query-string'
+import FixedBottom from '@/shared/components/custom/fixed-bottom'
+import Icon from '@/shared/components/custom/icon'
+import { Button } from '@/shared/components/ui/button'
+import {
+ Drawer,
+ DrawerClose,
+ DrawerContent,
+ DrawerHeader,
+ DrawerTitle,
+ DrawerTrigger,
+} from '@/shared/components/ui/drawer'
+import Text from '@/shared/components/ui/text'
+import { usePathname, useRouter, useSearchParams } from 'next/navigation'
+import { cn } from '@/shared/lib/utils'
+import { useState } from 'react'
+import { CATEGORIES } from '@/features/category/config'
+import { Checkbox } from '@/shared/components/ui/checkbox'
+import Label from '@/shared/components/ui/label'
+
+interface Props {
+ categories: Collection.Field[]
+}
+
+const SelectCategoryDrawer = ({ categories }: Props) => {
+ const [innerCategories, setInnerCategories] = useState(categories)
+ const searchParamsString = useSearchParams().toString()
+ const router = useRouter()
+ const pathname = usePathname()
+
+ const sortByCategories = (categories: Collection.Field[]) => {
+ const categoryOrder = CATEGORIES.map((category) => category.id)
+ return categories.sort((a, b) => categoryOrder.indexOf(a) - categoryOrder.indexOf(b))
+ }
+
+ return (
+
+
+
+
+
+
+
+ 분야
+
+
+
+
+
+ {CATEGORIES.map((category) => (
+
+ {
+ if (innerCategories.includes(category.id)) {
+ setInnerCategories(
+ sortByCategories(innerCategories.filter((c) => c !== category.id))
+ )
+ } else {
+ setInnerCategories(sortByCategories([...innerCategories, category.id]))
+ }
+ }}
+ />
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default SelectCategoryDrawer
diff --git a/src/features/collection/components/select-min-quiz-count-drawer.tsx b/src/features/collection/components/select-min-quiz-count-drawer.tsx
new file mode 100644
index 00000000..ab3ce691
--- /dev/null
+++ b/src/features/collection/components/select-min-quiz-count-drawer.tsx
@@ -0,0 +1,112 @@
+'use client'
+
+import QS from 'query-string'
+import FixedBottom from '@/shared/components/custom/fixed-bottom'
+import Icon from '@/shared/components/custom/icon'
+import { Button } from '@/shared/components/ui/button'
+import {
+ Drawer,
+ DrawerClose,
+ DrawerContent,
+ DrawerHeader,
+ DrawerTitle,
+ DrawerTrigger,
+} from '@/shared/components/ui/drawer'
+import { Slider } from '@/shared/components/ui/slider'
+import Text from '@/shared/components/ui/text'
+import { useState } from 'react'
+import { DEFAULT_COLLECTION_QUIZ_COUNT } from '../config'
+import { usePathname, useRouter, useSearchParams } from 'next/navigation'
+import { cn } from '@/shared/lib/utils'
+
+interface Props {
+ count: number
+}
+
+const SelectMinQuizCountDrawer = ({ count }: Props) => {
+ const [innerCount, setInnerCount] = useState(count)
+ const searchParamsString = useSearchParams().toString()
+ const router = useRouter()
+ const pathname = usePathname()
+
+ const isDefaultCount = innerCount === DEFAULT_COLLECTION_QUIZ_COUNT
+
+ return (
+
+
+
+
+
+
+
+ 문제 수
+
+
+
+
+
+ 최소 문제 수
+
+
+ {innerCount} 문제
+
+
+
{
+ if (value[0] == null || value[0] < DEFAULT_COLLECTION_QUIZ_COUNT) return
+
+ setInnerCount(value[0])
+ }}
+ />
+
+
+ 5 문제
+
+
+ 99 문제
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default SelectMinQuizCountDrawer
diff --git a/src/features/collection/components/select-quiz-type-drawer.tsx b/src/features/collection/components/select-quiz-type-drawer.tsx
new file mode 100644
index 00000000..89f7d2c9
--- /dev/null
+++ b/src/features/collection/components/select-quiz-type-drawer.tsx
@@ -0,0 +1,116 @@
+'use client'
+
+import QS from 'query-string'
+import FixedBottom from '@/shared/components/custom/fixed-bottom'
+import Icon from '@/shared/components/custom/icon'
+import { Button } from '@/shared/components/ui/button'
+import {
+ Drawer,
+ DrawerClose,
+ DrawerContent,
+ DrawerHeader,
+ DrawerTitle,
+ DrawerTrigger,
+} from '@/shared/components/ui/drawer'
+import Text from '@/shared/components/ui/text'
+import { usePathname, useRouter, useSearchParams } from 'next/navigation'
+import { cn } from '@/shared/lib/utils'
+import { RadioGroup, RadioGroupItem } from '@/shared/components/ui/radio-group'
+import Label from '@/shared/components/ui/label'
+import { useState } from 'react'
+
+interface Props {
+ quizType?: Quiz.Type
+}
+
+const SelectQuizTypeDrawer = ({ quizType }: Props) => {
+ const [innerQuizType, setInnerQuizType] = useState('all')
+ const searchParamsString = useSearchParams().toString()
+ const router = useRouter()
+ const pathname = usePathname()
+
+ return (
+
+
+
+
+
+
+
+ 유형
+
+
+
+
+
+
+
+ setInnerQuizType('all')} />
+
+
+
+ setInnerQuizType('MULTIPLE_CHOICE')}
+ />
+
+
+
+ setInnerQuizType('MIX_UP')}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export default SelectQuizTypeDrawer
diff --git a/src/features/collection/config/index.ts b/src/features/collection/config/index.ts
new file mode 100644
index 00000000..006acc3d
--- /dev/null
+++ b/src/features/collection/config/index.ts
@@ -0,0 +1 @@
+export const DEFAULT_COLLECTION_QUIZ_COUNT = 5
diff --git a/src/requests/collection/client.ts b/src/requests/collection/client.ts
index 640dae4b..ac1a724b 100644
--- a/src/requests/collection/client.ts
+++ b/src/requests/collection/client.ts
@@ -3,10 +3,15 @@
import { API_ENDPOINTS } from '@/shared/configs/endpoint'
import { http } from '@/shared/lib/axios/http'
-export const getAllCollections = async () => {
+export const getAllCollections = async (props?: {
+ collectionSortOption: 'POPULARITY' | 'UPDATED'
+ collectionCategories: Collection.Field[]
+ quizType?: 'MIX_UP' | 'MULTIPLE_CHOICE'
+ quizCount: number
+}) => {
try {
const { data } = await http.get(
- API_ENDPOINTS.COLLECTION.GET.ALL
+ API_ENDPOINTS.COLLECTION.GET.ALL(props)
)
return data
} catch (error) {
diff --git a/src/requests/collection/hooks.ts b/src/requests/collection/hooks.ts
index efb6c994..9ac552fd 100644
--- a/src/requests/collection/hooks.ts
+++ b/src/requests/collection/hooks.ts
@@ -11,10 +11,15 @@ import {
getRandomCollectionQuizzes,
} from './client'
-export const useCollections = () => {
+export const useCollections = (props?: {
+ collectionSortOption: 'POPULARITY' | 'UPDATED'
+ collectionCategories: Collection.Field[]
+ quizType?: 'MIX_UP' | 'MULTIPLE_CHOICE'
+ quizCount: number
+}) => {
return useQuery({
- queryKey: ['collections'],
- queryFn: async () => getAllCollections(),
+ queryKey: ['collections', JSON.stringify(props)],
+ queryFn: async () => getAllCollections(props),
})
}
diff --git a/src/shared/components/custom/fixed-bottom.tsx b/src/shared/components/custom/fixed-bottom.tsx
index 96fecab7..042ff027 100644
--- a/src/shared/components/custom/fixed-bottom.tsx
+++ b/src/shared/components/custom/fixed-bottom.tsx
@@ -1,5 +1,4 @@
import { cn } from '@/shared/lib/utils'
-import PortalProvider from './react/portal-provider'
interface Props {
children: React.ReactNode
@@ -8,16 +7,14 @@ interface Props {
const FixedBottom = ({ children, className }: Props) => {
return (
-
-
- {children}
-
-
+
+ {children}
+
)
}
diff --git a/src/shared/components/ui/radio-group.tsx b/src/shared/components/ui/radio-group.tsx
index fc8a1c47..8d71cdf7 100644
--- a/src/shared/components/ui/radio-group.tsx
+++ b/src/shared/components/ui/radio-group.tsx
@@ -22,7 +22,7 @@ const RadioGroupItem = React.forwardRef<
{
+ const query = props
+ ? QS.stringify({
+ 'collection-sort-option': props.collectionSortOption,
+ 'collection-category':
+ props.collectionCategories.length > 0
+ ? props.collectionCategories.join(',')
+ : undefined,
+ 'quiz-type': props.quizType,
+ 'quiz-count': props.quizCount,
+ })
+ : ''
+
+ return `/collections${query ? `?${query}` : ''}`
+ },
/** GET /collections/{keyword} - 컬렉션 검색하기 */
BY_KEYWORD: (keyword: string) => `/collections/${keyword}`,
/** GET /collections/{collection_id}/collection_info - 만든 컬렉션 상세 정보 가져오기 */