mirror of
https://github.com/ershisan99/flashcards-example-project.git
synced 2025-12-16 20:59:27 +00:00
feat: use search params for filters instead of redux
This commit is contained in:
@@ -43,7 +43,7 @@
|
||||
"@hookform/devtools": "^4.3.1",
|
||||
"@it-incubator/eslint-config": "^1.0.2",
|
||||
"@it-incubator/prettier-config": "^0.1.2",
|
||||
"@it-incubator/stylelint-config": "^1.0.1",
|
||||
"@it-incubator/stylelint-config": "^1.0.2",
|
||||
"@storybook/addon-essentials": "^7.6.6",
|
||||
"@storybook/addon-interactions": "^7.6.6",
|
||||
"@storybook/addon-links": "^7.6.6",
|
||||
|
||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -83,8 +83,8 @@ devDependencies:
|
||||
specifier: ^0.1.2
|
||||
version: 0.1.2
|
||||
'@it-incubator/stylelint-config':
|
||||
specifier: ^1.0.1
|
||||
version: 1.0.1(stylelint-config-clean-order@5.2.0)(stylelint-config-standard-scss@12.0.0)(stylelint@16.1.0)
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2(stylelint-config-clean-order@5.2.0)(stylelint-config-standard-scss@12.0.0)(stylelint@16.1.0)
|
||||
'@storybook/addon-essentials':
|
||||
specifier: ^7.6.6
|
||||
version: 7.6.6(@types/react-dom@18.2.18)(@types/react@18.2.46)(react-dom@18.2.0)(react@18.2.0)
|
||||
@@ -2178,8 +2178,8 @@ packages:
|
||||
prettier: 3.0.0
|
||||
dev: true
|
||||
|
||||
/@it-incubator/stylelint-config@1.0.1(stylelint-config-clean-order@5.2.0)(stylelint-config-standard-scss@12.0.0)(stylelint@16.1.0):
|
||||
resolution: {integrity: sha512-dZuCX0wXtuNwU0BoNM1fbfo9V9DIG4IhtZ67cOgTsv7/zXFOxXCPiOSx2SguCA7sg4P62/oq2pMQq/x/Y4Edag==}
|
||||
/@it-incubator/stylelint-config@1.0.2(stylelint-config-clean-order@5.2.0)(stylelint-config-standard-scss@12.0.0)(stylelint@16.1.0):
|
||||
resolution: {integrity: sha512-2LIducOMyfhFOGV1GvmpWjjvgRGhEtHw/qBv3Vjw+Nel8jCl9cpH2yJrdehK1QJSOkzvQGs2pPQViouW9FQUCA==}
|
||||
peerDependencies:
|
||||
stylelint: 16.1.0
|
||||
stylelint-config-clean-order: 5.2.0
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import s from './check-email.module.scss'
|
||||
|
||||
import { Email } from '../../../assets/icons'
|
||||
import { Button, Card, Typography } from '../../ui'
|
||||
|
||||
import s from './check-email.module.scss'
|
||||
|
||||
type Props = {
|
||||
email: string
|
||||
}
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
import { useForm } from 'react-hook-form'
|
||||
|
||||
import { Button, Card, ControlledTextField, Typography } from '../../ui'
|
||||
import { DevTool } from '@hookform/devtools'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { z } from 'zod'
|
||||
|
||||
import s from './new-password.module.scss'
|
||||
|
||||
import { Button, Card, ControlledTextField, Typography } from '../../ui'
|
||||
|
||||
const schema = z.object({
|
||||
password: z.string().nonempty('Enter password'),
|
||||
password: z.string().min(1, 'Enter password'),
|
||||
})
|
||||
|
||||
type FormType = z.infer<typeof schema>
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { Button, Card, ControlledCheckbox, ControlledTextField, Typography } from '../../ui'
|
||||
import { DevTool } from '@hookform/devtools'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { z } from 'zod'
|
||||
|
||||
import s from './sign-in.module.scss'
|
||||
|
||||
import { Button, Card, ControlledCheckbox, ControlledTextField, Typography } from '../../ui'
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email('Invalid email address').nonempty('Enter email'),
|
||||
password: z.string().nonempty('Enter password'),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { useForm } from 'react-hook-form'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { Button, Card, ControlledTextField, Typography } from '../../ui'
|
||||
import { DevTool } from '@hookform/devtools'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { omit } from 'remeda'
|
||||
@@ -9,6 +8,8 @@ import { z } from 'zod'
|
||||
|
||||
import s from './sign-up.module.scss'
|
||||
|
||||
import { Button, Card, ControlledTextField, Typography } from '../../ui'
|
||||
|
||||
const schema = z
|
||||
.object({
|
||||
email: z.string().email('Invalid email address').nonempty('Enter email'),
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
import { DeckDialog } from './'
|
||||
import { Button } from '@/components'
|
||||
import { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { DeckDialog } from './'
|
||||
|
||||
const meta = {
|
||||
component: DeckDialog,
|
||||
tags: ['autodocs'],
|
||||
|
||||
@@ -2,7 +2,9 @@ import { Link } from 'react-router-dom'
|
||||
|
||||
import { Edit2Outline, PlayCircleOutline, TrashOutline } from '@/assets'
|
||||
import {
|
||||
Button,
|
||||
Column,
|
||||
Sort,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
@@ -43,15 +45,24 @@ type Props = {
|
||||
decks: Deck[] | undefined
|
||||
onDeleteClick: (id: string) => void
|
||||
onEditClick: (id: string) => void
|
||||
onSort: (key: Sort) => void
|
||||
sort: Sort
|
||||
}
|
||||
|
||||
export const DecksTable = ({ currentUserId, decks, onDeleteClick, onEditClick }: Props) => {
|
||||
export const DecksTable = ({
|
||||
currentUserId,
|
||||
decks,
|
||||
onDeleteClick,
|
||||
onEditClick,
|
||||
onSort,
|
||||
sort,
|
||||
}: Props) => {
|
||||
const handleEditClick = (id: string) => () => onEditClick(id)
|
||||
const handleDeleteClick = (id: string) => () => onDeleteClick(id)
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<TableHeader columns={columns} />
|
||||
<TableHeader columns={columns} onSort={onSort} sort={sort} />
|
||||
<TableBody>
|
||||
{decks?.map(deck => (
|
||||
<TableRow key={deck.id}>
|
||||
@@ -65,17 +76,17 @@ export const DecksTable = ({ currentUserId, decks, onDeleteClick, onEditClick }:
|
||||
<TableCell>{deck.author.name}</TableCell>
|
||||
<TableCell>
|
||||
<div className={s.iconsContainer}>
|
||||
<Link to={`/decks/${deck.id}/learn`}>
|
||||
<Button as={Link} to={`/decks/${deck.id}/learn`} variant={'icon'}>
|
||||
<PlayCircleOutline />
|
||||
</Link>
|
||||
</Button>
|
||||
{deck.author.id === currentUserId && (
|
||||
<>
|
||||
<button onClick={handleEditClick(deck.id)}>
|
||||
<Button onClick={handleEditClick(deck.id)} variant={'icon'}>
|
||||
<Edit2Outline />
|
||||
</button>
|
||||
<button onClick={handleDeleteClick(deck.id)}>
|
||||
</Button>
|
||||
<Button onClick={handleDeleteClick(deck.id)} variant={'icon'}>
|
||||
<TrashOutline />
|
||||
</button>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
import { DeleteDeckDialog } from './'
|
||||
import { Button } from '@/components'
|
||||
import { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { DeleteDeckDialog } from './'
|
||||
|
||||
const meta = {
|
||||
component: DeleteDeckDialog,
|
||||
tags: ['autodocs'],
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import s from './personal-information.module.scss'
|
||||
|
||||
import { Camera, Edit, Logout } from '../../../assets/icons'
|
||||
import { Button, Card, Typography } from '../../ui'
|
||||
|
||||
import s from './personal-information.module.scss'
|
||||
|
||||
type Props = {
|
||||
avatar: string
|
||||
email: string
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
}
|
||||
|
||||
.primary {
|
||||
composes: button;
|
||||
color: var(--color-light-100);
|
||||
background-color: var(--color-accent-500);
|
||||
box-shadow: 0 4px 18px rgb(140 97 255 / 35%);
|
||||
@@ -58,6 +59,7 @@
|
||||
}
|
||||
|
||||
.secondary {
|
||||
composes: button;
|
||||
color: var(--color-light-100);
|
||||
background-color: var(--color-dark-300);
|
||||
box-shadow: 0 2px 10px 0 #6d6d6d40;
|
||||
@@ -72,6 +74,7 @@
|
||||
}
|
||||
|
||||
.tertiary {
|
||||
composes: button;
|
||||
color: var(--color-accent-500);
|
||||
background-color: var(--color-dark-900);
|
||||
border: 1px solid var(--color-accent-700);
|
||||
@@ -86,6 +89,8 @@
|
||||
}
|
||||
|
||||
.link {
|
||||
composes: button;
|
||||
|
||||
padding: 0.375rem 0;
|
||||
|
||||
font-weight: var(--font-weight-bold);
|
||||
@@ -93,3 +98,7 @@
|
||||
color: var(--color-accent-500);
|
||||
text-decoration-line: underline;
|
||||
}
|
||||
|
||||
.icon {
|
||||
border-radius: 9999px;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export type ButtonProps<T extends ElementType = 'button'> = {
|
||||
children: ReactNode
|
||||
className?: string
|
||||
fullWidth?: boolean
|
||||
variant?: 'link' | 'primary' | 'secondary' | 'tertiary'
|
||||
variant?: 'icon' | 'link' | 'primary' | 'secondary' | 'tertiary'
|
||||
} & ComponentPropsWithoutRef<T>
|
||||
|
||||
const ButtonPolymorph = <T extends ElementType = 'button'>(props: ButtonProps<T>, ref: any) => {
|
||||
@@ -31,7 +31,7 @@ const ButtonPolymorph = <T extends ElementType = 'button'>(props: ButtonProps<T>
|
||||
|
||||
return (
|
||||
<Component
|
||||
className={clsx(s.button, s[variant], fullWidth && s.fullWidth, className)}
|
||||
className={clsx(s[variant], fullWidth && s.fullWidth, className)}
|
||||
{...rest}
|
||||
ref={ref}
|
||||
/>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import type { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { Card } from './'
|
||||
import { Typography } from '@/components'
|
||||
|
||||
import { Card } from './'
|
||||
|
||||
const meta = {
|
||||
component: Card,
|
||||
tags: ['autodocs'],
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
import { Checkbox } from './checkbox'
|
||||
import { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { Checkbox } from './checkbox'
|
||||
const meta = {
|
||||
component: Checkbox,
|
||||
tags: ['autodocs'],
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
import { Dialog } from './'
|
||||
import { Meta, StoryObj } from '@storybook/react'
|
||||
|
||||
import { Dialog } from './'
|
||||
|
||||
const meta = {
|
||||
component: Dialog,
|
||||
tags: ['autodocs'],
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { FC } from 'react'
|
||||
|
||||
import { usePagination } from './usePagination'
|
||||
import { KeyboardArrowLeft, KeyboardArrowRight } from '@/assets'
|
||||
import { clsx } from 'clsx'
|
||||
|
||||
import s from './pagination.module.scss'
|
||||
|
||||
import { usePagination } from './usePagination'
|
||||
|
||||
type PaginationConditionals =
|
||||
| {
|
||||
onPerPageChange: (itemPerPage: number) => void
|
||||
|
||||
@@ -7,7 +7,7 @@ import s from './slider.module.scss'
|
||||
const Slider = forwardRef<
|
||||
ElementRef<typeof SliderPrimitive.Root>,
|
||||
Omit<ComponentPropsWithoutRef<typeof SliderPrimitive.Root>, 'value'> & {
|
||||
value?: (number | undefined)[]
|
||||
value?: (null | number)[]
|
||||
}
|
||||
>(({ className, max, onValueChange, value, ...props }, ref) => {
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeadCell, TableRow } from './'
|
||||
import { Typography } from '@/components'
|
||||
import { Meta } from '@storybook/react'
|
||||
|
||||
import { Table, TableBody, TableCell, TableEmpty, TableHead, TableHeadCell, TableRow } from './'
|
||||
|
||||
export default {
|
||||
component: Table,
|
||||
title: 'Components/Table',
|
||||
|
||||
1
src/hooks/index.ts
Normal file
1
src/hooks/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './use-query-param'
|
||||
1
src/hooks/use-query-param/index.ts
Normal file
1
src/hooks/use-query-param/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './use-query-param'
|
||||
36
src/hooks/use-query-param/use-query-param.ts
Normal file
36
src/hooks/use-query-param/use-query-param.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { isNil } from 'remeda'
|
||||
|
||||
export function useQueryParam<T extends boolean | number | string>(
|
||||
searchParams: URLSearchParams,
|
||||
setSearchParams: (searchParams: URLSearchParams) => void,
|
||||
param: string,
|
||||
defaultValue?: T
|
||||
): [T | null, (value: T | null) => void] {
|
||||
const paramValue = searchParams.get(param)
|
||||
const convertedValue = getConvertedValue<T>(paramValue, defaultValue)
|
||||
|
||||
const setParamValue = (value: T | null): void => {
|
||||
if (isNil(value) || value === '') {
|
||||
searchParams.delete(param)
|
||||
} else {
|
||||
searchParams.set(param, String(value))
|
||||
}
|
||||
setSearchParams(searchParams)
|
||||
}
|
||||
|
||||
return [convertedValue, setParamValue]
|
||||
}
|
||||
|
||||
function getConvertedValue<T>(value: null | string, defaultValue: T | undefined): T | null {
|
||||
if (value === null) {
|
||||
return defaultValue ?? null
|
||||
}
|
||||
if (value === 'true' || value === 'false') {
|
||||
return (value === 'true') as unknown as T
|
||||
}
|
||||
if (!isNaN(Number(value))) {
|
||||
return Number(value) as unknown as T
|
||||
}
|
||||
|
||||
return value as unknown as T
|
||||
}
|
||||
@@ -1,11 +1,12 @@
|
||||
import { StrictMode } from 'react'
|
||||
|
||||
import { App } from './App'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import './styles/index.scss'
|
||||
import '@fontsource/roboto/400.css'
|
||||
import '@fontsource/roboto/700.css'
|
||||
import './styles/index.scss'
|
||||
|
||||
import { App } from './App'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { DeckDialog } from '@/components/decks/deck-dialog'
|
||||
import { DeleteDeckDialog } from '@/components/decks/delete-deck-dialog'
|
||||
import { Pagination } from '@/components/ui/pagination'
|
||||
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||||
import { useDeckSearchParams } from '@/pages/decks-page/use-deck-search-params'
|
||||
import { useMeQuery } from '@/services/auth/auth.service'
|
||||
import {
|
||||
Tab,
|
||||
@@ -13,15 +14,6 @@ import {
|
||||
useGetDecksQuery,
|
||||
useUpdateDeckMutation,
|
||||
} from '@/services/decks'
|
||||
import {
|
||||
selectDecksCurrentPage,
|
||||
selectDecksCurrentTab,
|
||||
selectDecksMaxCards,
|
||||
selectDecksMinCards,
|
||||
selectDecksSearch,
|
||||
} from '@/services/decks/decks.selectors'
|
||||
import { decksSlice } from '@/services/decks/decks.slice'
|
||||
import { useAppDispatch, useAppSelector } from '@/services/store'
|
||||
|
||||
import s from './decks-page.module.scss'
|
||||
|
||||
@@ -33,40 +25,41 @@ export const DecksPage = () => {
|
||||
|
||||
const showEditModal = !!deckToEditId
|
||||
|
||||
const dispatch = useAppDispatch()
|
||||
const currentPage = useAppSelector(selectDecksCurrentPage)
|
||||
const minCards = useAppSelector(selectDecksMinCards)
|
||||
const maxCards = useAppSelector(selectDecksMaxCards)
|
||||
const currentTab = useAppSelector(selectDecksCurrentTab)
|
||||
const search = useAppSelector(selectDecksSearch)
|
||||
const setCurrentPage = (page: number) => dispatch(decksSlice.actions.setCurrentPage(page))
|
||||
const setMinCards = (minCards: number) => dispatch(decksSlice.actions.setMinCards(minCards))
|
||||
const setMaxCards = (maxCards: number) => dispatch(decksSlice.actions.setMaxCards(maxCards))
|
||||
const setSearch = (search: string) => dispatch(decksSlice.actions.setSearch(search))
|
||||
const setCurrentTab = (tab: { authorId?: string; tab: Tab }) =>
|
||||
dispatch(decksSlice.actions.setCurrentTab(tab))
|
||||
const {
|
||||
currentPage,
|
||||
currentTab,
|
||||
maxCardsCount,
|
||||
minCardsCount,
|
||||
rangeValue,
|
||||
search,
|
||||
setCurrentPage,
|
||||
setCurrentTab,
|
||||
setMaxCards,
|
||||
setMinCards,
|
||||
setRangeValue,
|
||||
setSearch,
|
||||
setSort,
|
||||
sort,
|
||||
} = useDeckSearchParams()
|
||||
|
||||
const resetFilters = () => {
|
||||
dispatch(decksSlice.actions.resetFilters())
|
||||
setRangeValue([0, decks?.maxCardsCount || undefined])
|
||||
}
|
||||
|
||||
const [rangeValue, setRangeValue] = useState([minCards, maxCards])
|
||||
|
||||
const handleSliderCommitted = (value: number[]) => {
|
||||
setMinCards(value[0])
|
||||
setMaxCards(value[1])
|
||||
}
|
||||
const currentUserId = me?.id
|
||||
const authorId = currentTab === 'my' ? currentUserId : undefined
|
||||
const { currentData: decksCurrentData, data: decksData } = useGetDecksQuery({
|
||||
authorId,
|
||||
currentPage,
|
||||
maxCardsCount: maxCards,
|
||||
minCardsCount: minCards,
|
||||
maxCardsCount,
|
||||
minCardsCount,
|
||||
name: search,
|
||||
orderBy: sort ? `${sort.key}-${sort.direction}` : undefined,
|
||||
})
|
||||
|
||||
const resetFilters = () => {
|
||||
setCurrentPage(null)
|
||||
setSearch(null)
|
||||
setMinCards(null)
|
||||
setMaxCards(null)
|
||||
setRangeValue([0, decks?.maxCardsCount ?? null])
|
||||
setSort(null)
|
||||
}
|
||||
const decks = decksCurrentData ?? decksData
|
||||
|
||||
const showConfirmDeleteModal = !!deckToDeleteId
|
||||
@@ -80,6 +73,16 @@ export const DecksPage = () => {
|
||||
|
||||
const openCreateModal = () => setShowCreateModal(true)
|
||||
|
||||
const handleSearch = (search: null | string) => {
|
||||
setCurrentPage(null)
|
||||
setSearch(search)
|
||||
}
|
||||
const handleSliderCommitted = (value: number[]) => {
|
||||
setCurrentPage(null)
|
||||
setMinCards(value[0])
|
||||
setMaxCards(value[1])
|
||||
}
|
||||
|
||||
if (!decks || !me) {
|
||||
return <Spinner fullScreen />
|
||||
}
|
||||
@@ -124,10 +127,15 @@ export const DecksPage = () => {
|
||||
/>
|
||||
</div>
|
||||
<div className={s.filters}>
|
||||
<TextField onValueChange={setSearch} placeholder={'Search'} search value={search} />
|
||||
<TextField
|
||||
onValueChange={handleSearch}
|
||||
placeholder={'Search'}
|
||||
search
|
||||
value={search ?? ''}
|
||||
/>
|
||||
<Tabs
|
||||
onValueChange={value => setCurrentTab({ authorId: currentUserId, tab: value as Tab })}
|
||||
value={currentTab}
|
||||
onValueChange={value => setCurrentTab(value as Tab)}
|
||||
value={currentTab ?? undefined}
|
||||
>
|
||||
<TabsList>
|
||||
<TabsTrigger value={'my'}>My decks</TabsTrigger>
|
||||
@@ -150,11 +158,13 @@ export const DecksPage = () => {
|
||||
decks={decks?.items}
|
||||
onDeleteClick={setDeckToDeleteId}
|
||||
onEditClick={setDeckToEditId}
|
||||
onSort={setSort}
|
||||
sort={sort}
|
||||
/>
|
||||
<Pagination
|
||||
count={decks?.pagination?.totalPages || 1}
|
||||
onChange={setCurrentPage}
|
||||
page={currentPage}
|
||||
page={currentPage ?? 1}
|
||||
/>
|
||||
</div>
|
||||
</Page>
|
||||
|
||||
75
src/pages/decks-page/use-deck-search-params.ts
Normal file
75
src/pages/decks-page/use-deck-search-params.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { useState } from 'react'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
|
||||
import { Sort } from '@/components'
|
||||
import { useQueryParam } from '@/hooks'
|
||||
|
||||
export function useDeckSearchParams() {
|
||||
const [searchParams, setSearchParams] = useSearchParams()
|
||||
const [currentPage, setCurrentPage] = useQueryParam<number>(
|
||||
searchParams,
|
||||
setSearchParams,
|
||||
'page',
|
||||
1
|
||||
)
|
||||
const [minCardsCount, setMinCards] = useQueryParam<number>(
|
||||
searchParams,
|
||||
setSearchParams,
|
||||
'minCards',
|
||||
0
|
||||
)
|
||||
const [maxCardsCount, setMaxCards] = useQueryParam<number>(
|
||||
searchParams,
|
||||
setSearchParams,
|
||||
'maxCards'
|
||||
)
|
||||
const [search, setSearch] = useQueryParam<string>(searchParams, setSearchParams, 'search')
|
||||
const [currentTab, setCurrentTab] = useQueryParam<string>(
|
||||
searchParams,
|
||||
setSearchParams,
|
||||
'currentTab',
|
||||
'all'
|
||||
)
|
||||
const [rangeValue, setRangeValue] = useState([minCardsCount, maxCardsCount])
|
||||
const [sortKey, setSortKey] = useQueryParam<string>(searchParams, setSearchParams, 'sortKey')
|
||||
const [sortDirection, setSortDirection] = useQueryParam<'asc' | 'desc'>(
|
||||
searchParams,
|
||||
setSearchParams,
|
||||
'sortDirection'
|
||||
)
|
||||
const setSort = (sort: Sort) => {
|
||||
if (!sort) {
|
||||
setSortKey(null)
|
||||
setSortDirection(null)
|
||||
|
||||
return
|
||||
}
|
||||
setSortKey(sort.key)
|
||||
setSortDirection(sort.direction)
|
||||
}
|
||||
|
||||
const sort: Sort =
|
||||
sortDirection === null || sortKey === null
|
||||
? null
|
||||
: {
|
||||
direction: sortDirection,
|
||||
key: sortKey,
|
||||
}
|
||||
|
||||
return {
|
||||
currentPage,
|
||||
currentTab,
|
||||
maxCardsCount,
|
||||
minCardsCount,
|
||||
rangeValue,
|
||||
search,
|
||||
setCurrentPage,
|
||||
setCurrentTab,
|
||||
setMaxCards,
|
||||
setMinCards,
|
||||
setRangeValue,
|
||||
setSearch,
|
||||
setSort,
|
||||
sort,
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
baseApi,
|
||||
} from '@/services'
|
||||
import { RootState } from '@/services/store'
|
||||
import { getValuable } from '@/utils'
|
||||
|
||||
const decksService = baseApi.injectEndpoints({
|
||||
endpoints: builder => ({
|
||||
@@ -16,13 +17,10 @@ const decksService = baseApi.injectEndpoints({
|
||||
async onQueryStarted(_, { dispatch, getState, queryFulfilled }) {
|
||||
const res = await queryFulfilled
|
||||
|
||||
console.log(decksService.util.selectCachedArgsForQuery(getState(), 'getDecks'))
|
||||
for (const { endpointName, originalArgs } of decksService.util.selectInvalidatedBy(
|
||||
getState(),
|
||||
[{ type: 'Decks' }]
|
||||
)) {
|
||||
console.log(endpointName, originalArgs)
|
||||
// we only want to update `getPosts` here
|
||||
if (endpointName !== 'getDecks') {
|
||||
continue
|
||||
}
|
||||
@@ -32,31 +30,6 @@ const decksService = baseApi.injectEndpoints({
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
// console.log(args)
|
||||
// const minCardsCount = state.decks.minCards
|
||||
// const search = state.decks.search
|
||||
// const currentPage = state.decks.currentPage
|
||||
// const maxCardsCount = state.decks.maxCards
|
||||
// const authorId = state.decks.authorId
|
||||
//
|
||||
// console.log(res)
|
||||
//
|
||||
// dispatch(
|
||||
// decksService.util.updateQueryData(
|
||||
// 'getDecks',
|
||||
// {
|
||||
// authorId,
|
||||
// currentPage,
|
||||
// maxCardsCount,
|
||||
// minCardsCount,
|
||||
// name: search,
|
||||
// },
|
||||
// draft => {
|
||||
// draft.items.unshift(res.data)
|
||||
// }
|
||||
// )
|
||||
// )
|
||||
},
|
||||
query: body => ({
|
||||
body,
|
||||
@@ -81,7 +54,7 @@ const decksService = baseApi.injectEndpoints({
|
||||
providesTags: ['Decks'],
|
||||
query: args => {
|
||||
return {
|
||||
params: args ?? undefined,
|
||||
params: args ? getValuable(args) : undefined,
|
||||
url: `v1/decks`,
|
||||
}
|
||||
},
|
||||
@@ -91,7 +64,6 @@ const decksService = baseApi.injectEndpoints({
|
||||
async onQueryStarted({ id, ...patch }, { dispatch, getState, queryFulfilled }) {
|
||||
const state = getState() as RootState
|
||||
|
||||
console.log(state)
|
||||
const minCardsCount = state.decks.minCards
|
||||
const search = state.decks.search
|
||||
const currentPage = state.decks.currentPage
|
||||
|
||||
@@ -54,13 +54,13 @@ export type Card = {
|
||||
}
|
||||
|
||||
export type GetDecksArgs = {
|
||||
authorId?: string
|
||||
currentPage?: number
|
||||
itemsPerPage?: number
|
||||
maxCardsCount?: number
|
||||
minCardsCount?: number
|
||||
name?: string
|
||||
orderBy?: string
|
||||
authorId?: null | string
|
||||
currentPage?: null | number
|
||||
itemsPerPage?: null | number
|
||||
maxCardsCount?: null | number
|
||||
minCardsCount?: null | number
|
||||
name?: null | string
|
||||
orderBy?: null | string
|
||||
}
|
||||
|
||||
export type CreateDeckArgs = {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
|
||||
|
||||
import { baseApi } from './base-api'
|
||||
import { decksSlice } from '@/services/decks/decks.slice'
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
import { setupListeners } from '@reduxjs/toolkit/query/react'
|
||||
|
||||
import { baseApi } from './base-api'
|
||||
|
||||
export const store = configureStore({
|
||||
middleware: getDefaultMiddleware => getDefaultMiddleware().concat(baseApi.middleware),
|
||||
reducer: {
|
||||
|
||||
9
src/utils/get-valuable.ts
Normal file
9
src/utils/get-valuable.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
type Valuable<T> = { [K in keyof T as T[K] extends null | undefined ? never : K]: T[K] }
|
||||
|
||||
export function getValuable<T extends {}, V = Valuable<T>>(obj: T): V {
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).filter(
|
||||
([, v]) => !((typeof v === 'string' && !v.length) || v === null || typeof v === 'undefined')
|
||||
)
|
||||
) as V
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export * from './date'
|
||||
export * from './get-valuable'
|
||||
|
||||
Reference in New Issue
Block a user