From 5e37027dbfc6d0bbb19c9486f2d4041c2f621c3d Mon Sep 17 00:00:00 2001 From: andres Date: Mon, 9 Oct 2023 12:13:45 +0200 Subject: [PATCH] add modals --- package.json | 1 + pnpm-lock.yaml | 106 ++++++++++++++++++ .../decks/deck-dialog/deck-dialog.module.scss | 8 ++ .../decks/deck-dialog/deck-dialog.stories.tsx | 74 ++++++++++++ .../decks/deck-dialog/deck-dialog.tsx | 52 +++++++++ src/components/decks/deck-dialog/index.ts | 1 + .../delete-deck-dialog.module.scss | 8 ++ .../delete-deck-dialog.stories.tsx | 41 +++++++ .../delete-deck-dialog/delete-deck-dialog.tsx | 19 ++++ .../decks/delete-deck-dialog/index.ts | 1 + src/components/ui/dialog/dialog.module.scss | 6 + src/components/ui/dialog/dialog.stories.tsx | 35 ++++++ src/components/ui/dialog/dialog.tsx | 30 +++++ src/components/ui/dialog/index.ts | 1 + src/components/ui/index.ts | 2 + src/components/ui/modal/index.ts | 1 + src/components/ui/modal/modal.module.scss | 41 +++++++ src/components/ui/modal/modal.stories.tsx | 35 ++++++ src/components/ui/modal/modal.tsx | 37 ++++++ src/components/ui/slider/slider.tsx | 45 +++++--- src/pages/decks-page/decks-page.tsx | 52 +++++++-- src/services/decks/decks.selectors.ts | 2 +- src/services/decks/decks.service.ts | 11 +- src/services/decks/decks.slice.ts | 14 ++- src/services/decks/decks.types.ts | 12 ++ 25 files changed, 600 insertions(+), 35 deletions(-) create mode 100644 src/components/decks/deck-dialog/deck-dialog.module.scss create mode 100644 src/components/decks/deck-dialog/deck-dialog.stories.tsx create mode 100644 src/components/decks/deck-dialog/deck-dialog.tsx create mode 100644 src/components/decks/deck-dialog/index.ts create mode 100644 src/components/decks/delete-deck-dialog/delete-deck-dialog.module.scss create mode 100644 src/components/decks/delete-deck-dialog/delete-deck-dialog.stories.tsx create mode 100644 src/components/decks/delete-deck-dialog/delete-deck-dialog.tsx create mode 100644 src/components/decks/delete-deck-dialog/index.ts create mode 100644 src/components/ui/dialog/dialog.module.scss create mode 100644 src/components/ui/dialog/dialog.stories.tsx create mode 100644 src/components/ui/dialog/dialog.tsx create mode 100644 src/components/ui/dialog/index.ts create mode 100644 src/components/ui/modal/index.ts create mode 100644 src/components/ui/modal/modal.module.scss create mode 100644 src/components/ui/modal/modal.stories.tsx create mode 100644 src/components/ui/modal/modal.tsx diff --git a/package.json b/package.json index 8fc62e6..ce6842a 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@fontsource/roboto": "^5.0.5", "@hookform/resolvers": "^3.1.1", "@radix-ui/react-checkbox": "^1.0.4", + "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-slider": "^1.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6cec91c..d99bad0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ dependencies: '@radix-ui/react-checkbox': specifier: ^1.0.4 version: 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-dialog': + specifier: ^1.0.5 + version: 1.0.5(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-label': specifier: ^2.0.2 version: 2.0.2(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) @@ -2256,6 +2259,40 @@ packages: '@types/react': 18.2.15 react: 18.2.0 + /@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@radix-ui/react-focus-scope': 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@radix-ui/react-portal': 1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-slot': 1.0.2(@types/react@18.2.15)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@types/react': 18.2.15 + '@types/react-dom': 18.2.7 + aria-hidden: 1.2.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.5(@types/react@18.2.15)(react@18.2.0) + dev: false + /@radix-ui/react-direction@1.0.1(@types/react@18.2.15)(react@18.2.0): resolution: {integrity: sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==} peerDependencies: @@ -2293,6 +2330,31 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + /@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.0.3(@types/react@18.2.15)(react@18.2.0) + '@types/react': 18.2.15 + '@types/react-dom': 18.2.7 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-focus-guards@1.0.1(@types/react@18.2.15)(react@18.2.0): resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} peerDependencies: @@ -2328,6 +2390,29 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + /@radix-ui/react-focus-scope@1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.15)(react@18.2.0) + '@types/react': 18.2.15 + '@types/react-dom': 18.2.7 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-id@1.0.1(@types/react@18.2.15)(react@18.2.0): resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} peerDependencies: @@ -2412,6 +2497,27 @@ packages: react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + /@radix-ui/react-portal@1.0.4(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.22.6 + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.2.15 + '@types/react-dom': 18.2.7 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-presence@1.0.1(@types/react-dom@18.2.7)(@types/react@18.2.15)(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} peerDependencies: diff --git a/src/components/decks/deck-dialog/deck-dialog.module.scss b/src/components/decks/deck-dialog/deck-dialog.module.scss new file mode 100644 index 0000000..72c4dec --- /dev/null +++ b/src/components/decks/deck-dialog/deck-dialog.module.scss @@ -0,0 +1,8 @@ +.content { + display: flex; + flex-direction: column; + gap: 24px; + + width: 100%; + padding: 24px; +} diff --git a/src/components/decks/deck-dialog/deck-dialog.stories.tsx b/src/components/decks/deck-dialog/deck-dialog.stories.tsx new file mode 100644 index 0000000..5f49f28 --- /dev/null +++ b/src/components/decks/deck-dialog/deck-dialog.stories.tsx @@ -0,0 +1,74 @@ +import { useState } from 'react' + +import { Meta, StoryObj } from '@storybook/react' + +import { DeckDialog } from './' + +import { Button } from '@/components' + +const meta = { + title: 'Decks/Deck Dialog', + component: DeckDialog, + tags: ['autodocs'], +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + open: true, + onOpenChange: () => {}, + }, + render: args => { + const [open, setOpen] = useState(false) + const closeModal = () => setOpen(false) + + return ( + <> + + { + console.log(data) + closeModal() + }} + /> + + ) + }, +} + +export const WithDefaultValues: Story = { + args: { + open: true, + onOpenChange: () => {}, + }, + render: args => { + const [open, setOpen] = useState(false) + const closeModal = () => setOpen(false) + + return ( + <> + + { + console.log(data) + closeModal() + }} + /> + + ) + }, +} diff --git a/src/components/decks/deck-dialog/deck-dialog.tsx b/src/components/decks/deck-dialog/deck-dialog.tsx new file mode 100644 index 0000000..e9b3897 --- /dev/null +++ b/src/components/decks/deck-dialog/deck-dialog.tsx @@ -0,0 +1,52 @@ +import { zodResolver } from '@hookform/resolvers/zod' +import { useForm } from 'react-hook-form' +import { z } from 'zod' + +import s from './deck-dialog.module.scss' + +import { ControlledCheckbox, ControlledTextField, Dialog, DialogProps } from '@/components' + +const newDeckSchema = z.object({ + name: z.string().min(3).max(50), + isPrivate: z.boolean(), +}) + +type FormValues = z.infer + +type Props = Pick & { + onConfirm: (data: FormValues) => void + defaultValues?: FormValues +} +export const DeckDialog = ({ + onConfirm, + onCancel, + defaultValues = { isPrivate: false, name: '' }, + ...dialogProps +}: Props) => { + const { control, handleSubmit, reset } = useForm({ + resolver: zodResolver(newDeckSchema), + defaultValues, + }) + const onSubmit = handleSubmit(data => { + onConfirm(data) + reset() + }) + const handleCancel = () => { + reset() + onCancel?.() + } + + return ( + +
+ + + +
+ ) +} diff --git a/src/components/decks/deck-dialog/index.ts b/src/components/decks/deck-dialog/index.ts new file mode 100644 index 0000000..7824814 --- /dev/null +++ b/src/components/decks/deck-dialog/index.ts @@ -0,0 +1 @@ +export * from './deck-dialog.tsx' diff --git a/src/components/decks/delete-deck-dialog/delete-deck-dialog.module.scss b/src/components/decks/delete-deck-dialog/delete-deck-dialog.module.scss new file mode 100644 index 0000000..0aeb293 --- /dev/null +++ b/src/components/decks/delete-deck-dialog/delete-deck-dialog.module.scss @@ -0,0 +1,8 @@ +.content { + padding: 18px 24px; + + > p { + margin: 0; + padding: 0; + } +} diff --git a/src/components/decks/delete-deck-dialog/delete-deck-dialog.stories.tsx b/src/components/decks/delete-deck-dialog/delete-deck-dialog.stories.tsx new file mode 100644 index 0000000..8d2c59e --- /dev/null +++ b/src/components/decks/delete-deck-dialog/delete-deck-dialog.stories.tsx @@ -0,0 +1,41 @@ +import { useState } from 'react' + +import { Meta, StoryObj } from '@storybook/react' + +import { DeleteDeckDialog } from './' + +import { Button } from '@/components' + +const meta = { + title: 'Decks/Delete Deck Dialog', + component: DeleteDeckDialog, + tags: ['autodocs'], +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + deckName: 'Deck Name', + open: true, + onOpenChange: () => {}, + }, + render: args => { + const [open, setOpen] = useState(false) + const closeModal = () => setOpen(false) + + return ( + <> + + + + ) + }, +} diff --git a/src/components/decks/delete-deck-dialog/delete-deck-dialog.tsx b/src/components/decks/delete-deck-dialog/delete-deck-dialog.tsx new file mode 100644 index 0000000..185f5df --- /dev/null +++ b/src/components/decks/delete-deck-dialog/delete-deck-dialog.tsx @@ -0,0 +1,19 @@ +import s from './delete-deck-dialog.module.scss' + +import { Dialog, DialogProps } from '@/components' +export default {} +type Props = Pick & { + deckName: string +} +export const DeleteDeckDialog = ({ deckName, ...dialogProps }: Props) => { + return ( + +
+

+ Do you really want to remove {deckName}? +

+

All cards will be deleted.

+
+
+ ) +} diff --git a/src/components/decks/delete-deck-dialog/index.ts b/src/components/decks/delete-deck-dialog/index.ts new file mode 100644 index 0000000..52fbdd9 --- /dev/null +++ b/src/components/decks/delete-deck-dialog/index.ts @@ -0,0 +1 @@ +export * from './delete-deck-dialog' diff --git a/src/components/ui/dialog/dialog.module.scss b/src/components/ui/dialog/dialog.module.scss new file mode 100644 index 0000000..229fd2c --- /dev/null +++ b/src/components/ui/dialog/dialog.module.scss @@ -0,0 +1,6 @@ +.buttons { + display: flex; + align-items: center; + justify-content: space-between; + padding: 12px 24px 36px; +} diff --git a/src/components/ui/dialog/dialog.stories.tsx b/src/components/ui/dialog/dialog.stories.tsx new file mode 100644 index 0000000..7c870d5 --- /dev/null +++ b/src/components/ui/dialog/dialog.stories.tsx @@ -0,0 +1,35 @@ +import { useState } from 'react' + +import { Meta, StoryObj } from '@storybook/react' + +import { Dialog } from './' + +const meta = { + title: 'Components/Dialog', + component: Dialog, + tags: ['autodocs'], +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + open: true, + onOpenChange: () => {}, + title: 'Modal', + children: 'Modal', + }, + render: args => { + const [open, setOpen] = useState(false) + + return ( + <> + + + Dialog content here + + + ) + }, +} diff --git a/src/components/ui/dialog/dialog.tsx b/src/components/ui/dialog/dialog.tsx new file mode 100644 index 0000000..8ac71e0 --- /dev/null +++ b/src/components/ui/dialog/dialog.tsx @@ -0,0 +1,30 @@ +import s from './dialog.module.scss' + +import { Button, Modal, ModalProps } from '@/components' + +export type DialogProps = ModalProps & { + confirmText?: string + cancelText?: string + onConfirm?: () => void + onCancel?: () => void +} +export const Dialog = ({ + children, + onCancel, + onConfirm, + confirmText = 'OK', + cancelText = 'Cancel', + ...modalProps +}: DialogProps) => { + return ( + + {children} +
+ + +
+
+ ) +} diff --git a/src/components/ui/dialog/index.ts b/src/components/ui/dialog/index.ts new file mode 100644 index 0000000..20033cb --- /dev/null +++ b/src/components/ui/dialog/index.ts @@ -0,0 +1 @@ +export * from './dialog' diff --git a/src/components/ui/index.ts b/src/components/ui/index.ts index d5a1afb..63527e7 100644 --- a/src/components/ui/index.ts +++ b/src/components/ui/index.ts @@ -2,7 +2,9 @@ export * from './button' export * from './card' export * from './typography' export * from './checkbox' +export * from './dialog' export * from './text-field' +export * from './modal' export * from './table' export * from './controlled' export * from './radio-group' diff --git a/src/components/ui/modal/index.ts b/src/components/ui/modal/index.ts new file mode 100644 index 0000000..cdbd4fb --- /dev/null +++ b/src/components/ui/modal/index.ts @@ -0,0 +1 @@ +export * from './modal' diff --git a/src/components/ui/modal/modal.module.scss b/src/components/ui/modal/modal.module.scss new file mode 100644 index 0000000..d5c5683 --- /dev/null +++ b/src/components/ui/modal/modal.module.scss @@ -0,0 +1,41 @@ +.overlay { + position: fixed; + inset: 0; + background-color: rgb(0 0 0 / 70%); +} + +.content { + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + + width: 90vw; + max-width: 542px; + max-height: 85vh; + + background-color: var(--color-dark-700); + border: 1px solid var(--color-dark-500, #333); + border-radius: 2px; +} + +.header { + display: flex; + align-items: center; + justify-content: space-between; + + padding: 18px 24px; + + border-bottom: 1px solid var(--color-dark-500, #333); +} + +.closeButton { + all: unset; + + cursor: pointer; + + display: flex; + + width: 24px; + height: 24px; +} diff --git a/src/components/ui/modal/modal.stories.tsx b/src/components/ui/modal/modal.stories.tsx new file mode 100644 index 0000000..8ef5994 --- /dev/null +++ b/src/components/ui/modal/modal.stories.tsx @@ -0,0 +1,35 @@ +import { useState } from 'react' + +import { Meta, StoryObj } from '@storybook/react' + +import { Modal } from '@/components' + +const meta = { + title: 'Components/Modal', + component: Modal, + tags: ['autodocs'], +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = { + args: { + open: true, + onOpenChange: () => {}, + title: 'Modal', + children: 'Modal', + }, + render: args => { + const [open, setOpen] = useState(false) + + return ( + <> + + + Modal content here + + + ) + }, +} diff --git a/src/components/ui/modal/modal.tsx b/src/components/ui/modal/modal.tsx new file mode 100644 index 0000000..9b346bc --- /dev/null +++ b/src/components/ui/modal/modal.tsx @@ -0,0 +1,37 @@ +import { ComponentPropsWithoutRef, ReactNode } from 'react' + +import * as DialogPrimitive from '@radix-ui/react-dialog' + +import s from './modal.module.scss' + +import { Close } from '@/assets' +import { Typography } from '@/components' + +export type ModalProps = { + title?: string + open: boolean + onOpenChange: (open: boolean) => void + children: ReactNode +} & Omit, 'open' | 'onOpenChange'> +export const Modal = ({ children, title, ...props }: ModalProps) => { + return ( + + + + +
+ + + {title} + + + + + +
+ {children} +
+
+
+ ) +} diff --git a/src/components/ui/slider/slider.tsx b/src/components/ui/slider/slider.tsx index e19137f..abcc5cd 100644 --- a/src/components/ui/slider/slider.tsx +++ b/src/components/ui/slider/slider.tsx @@ -1,4 +1,4 @@ -import { ComponentPropsWithoutRef, ElementRef, forwardRef } from 'react' +import { ComponentPropsWithoutRef, ElementRef, forwardRef, useEffect } from 'react' import * as SliderPrimitive from '@radix-ui/react-slider' import { clsx } from 'clsx' @@ -6,20 +6,35 @@ import { clsx } from 'clsx' import s from './slider.module.scss' const Slider = forwardRef< ElementRef, - ComponentPropsWithoutRef ->(({ className, ...props }, ref) => ( -
- {props?.value?.[0]} - - - - - - - - {props?.value?.[1]} -
-)) + Omit, 'value'> & { + value?: (number | undefined)[] + } +>(({ className, value, ...props }, ref) => { + useEffect(() => { + if (value?.[1] === undefined || value?.[1] === null) { + props.onValueChange?.([value?.[0] ?? 0, props.max ?? 0]) + } + }, [props.max, value]) + + return ( +
+ {value?.[0]} + + + + + + + + {value?.[1]} +
+ ) +}) Slider.displayName = SliderPrimitive.Root.displayName diff --git a/src/pages/decks-page/decks-page.tsx b/src/pages/decks-page/decks-page.tsx index 7ac4624..bc4192e 100644 --- a/src/pages/decks-page/decks-page.tsx +++ b/src/pages/decks-page/decks-page.tsx @@ -3,23 +3,49 @@ import { useState } from 'react' import s from './decks-page.module.scss' import { Button, Page, Slider, TextField, Typography } from '@/components' -import { DecksTable } from '@/components/decks/decks-table.tsx' +import { DecksTable } from '@/components/decks/decks-table' import { Pagination } from '@/components/ui/pagination' import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs' -import { useGetDecksQuery } from '@/services/decks' -import { selectDecksCurrentPage } from '@/services/decks/decks.selectors.ts' +import { Tab, useGetDecksQuery } from '@/services/decks' +import { + selectDecksCurrentPage, + selectDecksCurrentTab, + selectDecksMaxCards, + selectDecksMinCards, + selectDecksSearch, +} from '@/services/decks/decks.selectors.ts' import { decksSlice } from '@/services/decks/decks.slice.ts' import { useAppDispatch, useAppSelector } from '@/services/store.ts' export const DecksPage = () => { 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: Tab) => dispatch(decksSlice.actions.setCurrentTab(tab)) - const { data: decks } = useGetDecksQuery() - const [activeTab, setActiveTab] = useState('my') - const [range, setRange] = useState([0, 100]) - const [rangeValue, setRangeValue] = useState([0, 1]) + const [rangeValue, setRangeValue] = useState([minCards, maxCards]) + + const handleSliderCommitted = (value: number[]) => { + setMinCards(value[0]) + setMaxCards(value[1]) + } + + const authorId = currentTab === 'my' ? 'f2be95b9-4d07-4751-a775-bd612fc9553a' : undefined + + const { data: decks } = useGetDecksQuery({ + currentPage, + minCardsCount: minCards, + maxCardsCount: maxCards, + name: search, + authorId, + }) if (!decks) return
loading...
@@ -31,14 +57,20 @@ export const DecksPage = () => {
- - + + setCurrentTab(value as Tab)}> My decks All decks - +
diff --git a/src/services/decks/decks.selectors.ts b/src/services/decks/decks.selectors.ts index 2a14de6..7d1e844 100644 --- a/src/services/decks/decks.selectors.ts +++ b/src/services/decks/decks.selectors.ts @@ -6,7 +6,7 @@ export const selectDecksPerPage = (state: RootState) => state.decks.perPage export const selectDecksSearch = (state: RootState) => state.decks.search -export const selectDecksAuthorId = (state: RootState) => state.decks.authorId +export const selectDecksCurrentTab = (state: RootState) => state.decks.currentTab export const selectDecksMinCards = (state: RootState) => state.decks.minCards diff --git a/src/services/decks/decks.service.ts b/src/services/decks/decks.service.ts index 39c3d53..a448456 100644 --- a/src/services/decks/decks.service.ts +++ b/src/services/decks/decks.service.ts @@ -1,11 +1,16 @@ -import { CardsResponse, DeckResponse, DecksResponse } from './decks.types' +import { CardsResponse, DeckResponse, DecksResponse, GetDecksArgs } from './decks.types' import { baseApi } from '@/services' const decksService = baseApi.injectEndpoints({ endpoints: builder => ({ - getDecks: builder.query({ - query: () => `v1/decks`, + getDecks: builder.query({ + query: args => { + return { + url: `v1/decks`, + params: args ?? undefined, + } + }, }), getDeckById: builder.query({ query: ({ id }) => `v1/decks/${id}`, diff --git a/src/services/decks/decks.slice.ts b/src/services/decks/decks.slice.ts index a6129b1..504aa63 100644 --- a/src/services/decks/decks.slice.ts +++ b/src/services/decks/decks.slice.ts @@ -1,14 +1,16 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' +import { Tab } from '@/services' + export const decksSlice = createSlice({ name: 'decks', initialState: { currentPage: 1, perPage: 10, search: '', - authorId: '', minCards: 0, - maxCards: null as number | null, + maxCards: undefined as number | undefined, + currentTab: 'all' as Tab, }, reducers: { setCurrentPage: (state, action: PayloadAction) => { @@ -20,8 +22,8 @@ export const decksSlice = createSlice({ setSearch: (state, action: PayloadAction) => { state.search = action.payload }, - setAuthorId: (state, action: PayloadAction) => { - state.authorId = action.payload + setCurrentTab: (state, action: PayloadAction) => { + state.currentTab = action.payload }, setMinCards: (state, action: PayloadAction) => { state.minCards = action.payload @@ -31,9 +33,9 @@ export const decksSlice = createSlice({ }, resetFilters: state => { state.search = '' - state.authorId = '' + state.currentTab = 'all' state.minCards = 0 - state.maxCards = null + state.maxCards = undefined }, resetCurrentPage: state => { state.currentPage = 1 diff --git a/src/services/decks/decks.types.ts b/src/services/decks/decks.types.ts index 7ee9a3c..a66663d 100644 --- a/src/services/decks/decks.types.ts +++ b/src/services/decks/decks.types.ts @@ -52,3 +52,15 @@ export type Card = { grade: number userId: string } + +export type GetDecksArgs = { + minCardsCount?: number + maxCardsCount?: number + name?: string + authorId?: string + orderBy?: string + currentPage?: number + itemsPerPage?: number +} + +export type Tab = 'all' | 'my'